internal virtual SimpleTensor RandomBinaryTensor() { double range = 1.0 / (4.0 * numHid); SimpleTensor tensor = SimpleTensor.Random(numHid * 2, numHid * 2, numHid, -range, range, rand); return(tensor.Scale(op.trainOptions.scalingForInit)); }
private static SimpleTensor GetTensorGradient(SimpleMatrix deltaFull, SimpleMatrix leftVector, SimpleMatrix rightVector) { int size = deltaFull.GetNumElements(); SimpleTensor Wt_df = new SimpleTensor(size * 2, size * 2, size); // TODO: combine this concatenation with computeTensorDeltaDown? SimpleMatrix fullVector = NeuralUtils.Concatenate(leftVector, rightVector); for (int slice = 0; slice < size; ++slice) { Wt_df.SetSlice(slice, fullVector.Scale(deltaFull.Get(slice)).Mult(fullVector.Transpose())); } return(Wt_df); }
private static double ScaleAndRegularizeTensor(TwoDimensionalMap <string, string, SimpleTensor> derivatives, TwoDimensionalMap <string, string, SimpleTensor> currentMatrices, double scale, double regCost) { double cost = 0.0; // the regularization cost foreach (TwoDimensionalMap.Entry <string, string, SimpleTensor> entry in currentMatrices) { SimpleTensor D = derivatives.Get(entry.GetFirstKey(), entry.GetSecondKey()); D = D.Scale(scale).Plus(entry.GetValue().Scale(regCost)); derivatives.Put(entry.GetFirstKey(), entry.GetSecondKey(), D); cost += entry.GetValue().ElementMult(entry.GetValue()).ElementSum() * regCost / 2.0; } return(cost); }
public virtual void VectorToParams(double[] theta) { NeuralUtils.VectorToParams(theta, binaryTransform.ValueIterator(), binaryClassification.ValueIterator(), SimpleTensor.IteratorSimpleMatrix(binaryTensors.ValueIterator()), unaryClassification.Values.GetEnumerator(), wordVectors.Values.GetEnumerator ()); }
public virtual double[] ParamsToVector() { int totalSize = TotalParamSize(); return(NeuralUtils.ParamsToVector(totalSize, binaryTransform.ValueIterator(), binaryClassification.ValueIterator(), SimpleTensor.IteratorSimpleMatrix(binaryTensors.ValueIterator()), unaryClassification.Values.GetEnumerator(), wordVectors.Values .GetEnumerator())); }
/* * // An example of how you could read in old models with readObject to fix the serialization * // You would first read in the old model, then reserialize it * private void readObject(ObjectInputStream in) * throws IOException, ClassNotFoundException * { * ObjectInputStream.GetField fields = in.readFields(); * binaryTransform = ErasureUtils.uncheckedCast(fields.get("binaryTransform", null)); * * // transform binaryTensors * binaryTensors = TwoDimensionalMap.treeMap(); * TwoDimensionalMap<String, String, edu.stanford.nlp.rnn.SimpleTensor> oldTensors = ErasureUtils.uncheckedCast(fields.get("binaryTensors", null)); * for (String first : oldTensors.firstKeySet()) { * for (String second : oldTensors.get(first).keySet()) { * binaryTensors.put(first, second, new SimpleTensor(oldTensors.get(first, second).slices)); * } * } * * binaryClassification = ErasureUtils.uncheckedCast(fields.get("binaryClassification", null)); * unaryClassification = ErasureUtils.uncheckedCast(fields.get("unaryClassification", null)); * wordVectors = ErasureUtils.uncheckedCast(fields.get("wordVectors", null)); * * if (fields.defaulted("numClasses")) { * throw new RuntimeException(); * } * numClasses = fields.get("numClasses", 0); * * if (fields.defaulted("numHid")) { * throw new RuntimeException(); * } * numHid = fields.get("numHid", 0); * * if (fields.defaulted("numBinaryMatrices")) { * throw new RuntimeException(); * } * numBinaryMatrices = fields.get("numBinaryMatrices", 0); * * if (fields.defaulted("binaryTransformSize")) { * throw new RuntimeException(); * } * binaryTransformSize = fields.get("binaryTransformSize", 0); * * if (fields.defaulted("binaryTensorSize")) { * throw new RuntimeException(); * } * binaryTensorSize = fields.get("binaryTensorSize", 0); * * if (fields.defaulted("binaryClassificationSize")) { * throw new RuntimeException(); * } * binaryClassificationSize = fields.get("binaryClassificationSize", 0); * * if (fields.defaulted("numUnaryMatrices")) { * throw new RuntimeException(); * } * numUnaryMatrices = fields.get("numUnaryMatrices", 0); * * if (fields.defaulted("unaryClassificationSize")) { * throw new RuntimeException(); * } * unaryClassificationSize = fields.get("unaryClassificationSize", 0); * * rand = ErasureUtils.uncheckedCast(fields.get("rand", null)); * op = ErasureUtils.uncheckedCast(fields.get("op", null)); * op.classNames = op.DEFAULT_CLASS_NAMES; * op.equivalenceClasses = op.APPROXIMATE_EQUIVALENCE_CLASSES; * op.equivalenceClassNames = op.DEFAULT_EQUIVALENCE_CLASS_NAMES; * } */ /// <summary> /// Given single matrices and sets of options, create the /// corresponding SentimentModel. /// </summary> /// <remarks> /// Given single matrices and sets of options, create the /// corresponding SentimentModel. Useful for creating a Java version /// of a model trained in some other manner, such as using the /// original Matlab code. /// </remarks> internal static Edu.Stanford.Nlp.Sentiment.SentimentModel ModelFromMatrices(SimpleMatrix W, SimpleMatrix Wcat, SimpleTensor Wt, IDictionary <string, SimpleMatrix> wordVectors, RNNOptions op) { if (!op.combineClassification || !op.simplifiedModel) { throw new ArgumentException("Can only create a model using this method if combineClassification and simplifiedModel are turned on"); } TwoDimensionalMap <string, string, SimpleMatrix> binaryTransform = TwoDimensionalMap.TreeMap(); binaryTransform.Put(string.Empty, string.Empty, W); TwoDimensionalMap <string, string, SimpleTensor> binaryTensors = TwoDimensionalMap.TreeMap(); binaryTensors.Put(string.Empty, string.Empty, Wt); TwoDimensionalMap <string, string, SimpleMatrix> binaryClassification = TwoDimensionalMap.TreeMap(); IDictionary <string, SimpleMatrix> unaryClassification = Generics.NewTreeMap(); unaryClassification[string.Empty] = Wcat; return(new Edu.Stanford.Nlp.Sentiment.SentimentModel(binaryTransform, binaryTensors, binaryClassification, unaryClassification, wordVectors, op)); }
/// <summary> /// This is the method to call for assigning labels and node vectors /// to the Tree. /// </summary> /// <remarks> /// This is the method to call for assigning labels and node vectors /// to the Tree. After calling this, each of the non-leaf nodes will /// have the node vector and the predictions of their classes /// assigned to that subtree's node. The annotations filled in are /// the RNNCoreAnnotations.NodeVector, Predictions, and /// PredictedClass. In general, PredictedClass will be the most /// useful annotation except when training. /// </remarks> public virtual void ForwardPropagateTree(Tree tree) { SimpleMatrix nodeVector; // initialized below or Exception thrown // = null; SimpleMatrix classification; // initialized below or Exception thrown // = null; if (tree.IsLeaf()) { // We do nothing for the leaves. The preterminals will // calculate the classification for this word/tag. In fact, the // recursion should not have gotten here (unless there are // degenerate trees of just one leaf) log.Info("SentimentCostAndGradient: warning: We reached leaves in forwardPropagate: " + tree); throw new AssertionError("We should not have reached leaves in forwardPropagate"); } else { if (tree.IsPreTerminal()) { classification = model.GetUnaryClassification(tree.Label().Value()); string word = tree.Children()[0].Label().Value(); SimpleMatrix wordVector = model.GetWordVector(word); nodeVector = NeuralUtils.ElementwiseApplyTanh(wordVector); } else { if (tree.Children().Length == 1) { log.Info("SentimentCostAndGradient: warning: Non-preterminal nodes of size 1: " + tree); throw new AssertionError("Non-preterminal nodes of size 1 should have already been collapsed"); } else { if (tree.Children().Length == 2) { ForwardPropagateTree(tree.Children()[0]); ForwardPropagateTree(tree.Children()[1]); string leftCategory = tree.Children()[0].Label().Value(); string rightCategory = tree.Children()[1].Label().Value(); SimpleMatrix W = model.GetBinaryTransform(leftCategory, rightCategory); classification = model.GetBinaryClassification(leftCategory, rightCategory); SimpleMatrix leftVector = RNNCoreAnnotations.GetNodeVector(tree.Children()[0]); SimpleMatrix rightVector = RNNCoreAnnotations.GetNodeVector(tree.Children()[1]); SimpleMatrix childrenVector = NeuralUtils.ConcatenateWithBias(leftVector, rightVector); if (model.op.useTensors) { SimpleTensor tensor = model.GetBinaryTensor(leftCategory, rightCategory); SimpleMatrix tensorIn = NeuralUtils.Concatenate(leftVector, rightVector); SimpleMatrix tensorOut = tensor.BilinearProducts(tensorIn); nodeVector = NeuralUtils.ElementwiseApplyTanh(W.Mult(childrenVector).Plus(tensorOut)); } else { nodeVector = NeuralUtils.ElementwiseApplyTanh(W.Mult(childrenVector)); } } else { log.Info("SentimentCostAndGradient: warning: Tree not correctly binarized: " + tree); throw new AssertionError("Tree not correctly binarized"); } } } } SimpleMatrix predictions = NeuralUtils.Softmax(classification.Mult(NeuralUtils.ConcatenateWithBias(nodeVector))); int index = GetPredictedClass(predictions); if (!(tree.Label() is CoreLabel)) { log.Info("SentimentCostAndGradient: warning: No CoreLabels in nodes: " + tree); throw new AssertionError("Expected CoreLabels in the nodes"); } CoreLabel label = (CoreLabel)tree.Label(); label.Set(typeof(RNNCoreAnnotations.Predictions), predictions); label.Set(typeof(RNNCoreAnnotations.PredictedClass), index); label.Set(typeof(RNNCoreAnnotations.NodeVector), nodeVector); }
private static SimpleMatrix ComputeTensorDeltaDown(SimpleMatrix deltaFull, SimpleMatrix leftVector, SimpleMatrix rightVector, SimpleMatrix W, SimpleTensor Wt) { SimpleMatrix WTDelta = W.Transpose().Mult(deltaFull); SimpleMatrix WTDeltaNoBias = WTDelta.ExtractMatrix(0, deltaFull.NumRows() * 2, 0, 1); int size = deltaFull.GetNumElements(); SimpleMatrix deltaTensor = new SimpleMatrix(size * 2, 1); SimpleMatrix fullVector = NeuralUtils.Concatenate(leftVector, rightVector); for (int slice = 0; slice < size; ++slice) { SimpleMatrix scaledFullVector = fullVector.Scale(deltaFull.Get(slice)); deltaTensor = deltaTensor.Plus(Wt.GetSlice(slice).Plus(Wt.GetSlice(slice).Transpose()).Mult(scaledFullVector)); } return(deltaTensor.Plus(WTDeltaNoBias)); }
private void BackpropDerivativesAndError(Tree tree, TwoDimensionalMap <string, string, SimpleMatrix> binaryTD, TwoDimensionalMap <string, string, SimpleMatrix> binaryCD, TwoDimensionalMap <string, string, SimpleTensor> binaryTensorTD, IDictionary <string, SimpleMatrix> unaryCD, IDictionary <string, SimpleMatrix> wordVectorD, SimpleMatrix deltaUp) { if (tree.IsLeaf()) { return; } SimpleMatrix currentVector = RNNCoreAnnotations.GetNodeVector(tree); string category = tree.Label().Value(); category = model.BasicCategory(category); // Build a vector that looks like 0,0,1,0,0 with an indicator for the correct class SimpleMatrix goldLabel = new SimpleMatrix(model.numClasses, 1); int goldClass = RNNCoreAnnotations.GetGoldClass(tree); if (goldClass >= 0) { goldLabel.Set(goldClass, 1.0); } double nodeWeight = model.op.trainOptions.GetClassWeight(goldClass); SimpleMatrix predictions = RNNCoreAnnotations.GetPredictions(tree); // If this is an unlabeled class, set deltaClass to 0. We could // make this more efficient by eliminating various of the below // calculations, but this would be the easiest way to handle the // unlabeled class SimpleMatrix deltaClass = goldClass >= 0 ? predictions.Minus(goldLabel).Scale(nodeWeight) : new SimpleMatrix(predictions.NumRows(), predictions.NumCols()); SimpleMatrix localCD = deltaClass.Mult(NeuralUtils.ConcatenateWithBias(currentVector).Transpose()); double error = -(NeuralUtils.ElementwiseApplyLog(predictions).ElementMult(goldLabel).ElementSum()); error = error * nodeWeight; RNNCoreAnnotations.SetPredictionError(tree, error); if (tree.IsPreTerminal()) { // below us is a word vector unaryCD[category] = unaryCD[category].Plus(localCD); string word = tree.Children()[0].Label().Value(); word = model.GetVocabWord(word); //SimpleMatrix currentVectorDerivative = NeuralUtils.elementwiseApplyTanhDerivative(currentVector); //SimpleMatrix deltaFromClass = model.getUnaryClassification(category).transpose().mult(deltaClass); //SimpleMatrix deltaFull = deltaFromClass.extractMatrix(0, model.op.numHid, 0, 1).plus(deltaUp); //SimpleMatrix wordDerivative = deltaFull.elementMult(currentVectorDerivative); //wordVectorD.put(word, wordVectorD.get(word).plus(wordDerivative)); SimpleMatrix currentVectorDerivative = NeuralUtils.ElementwiseApplyTanhDerivative(currentVector); SimpleMatrix deltaFromClass = model.GetUnaryClassification(category).Transpose().Mult(deltaClass); deltaFromClass = deltaFromClass.ExtractMatrix(0, model.op.numHid, 0, 1).ElementMult(currentVectorDerivative); SimpleMatrix deltaFull = deltaFromClass.Plus(deltaUp); SimpleMatrix oldWordVectorD = wordVectorD[word]; if (oldWordVectorD == null) { wordVectorD[word] = deltaFull; } else { wordVectorD[word] = oldWordVectorD.Plus(deltaFull); } } else { // Otherwise, this must be a binary node string leftCategory = model.BasicCategory(tree.Children()[0].Label().Value()); string rightCategory = model.BasicCategory(tree.Children()[1].Label().Value()); if (model.op.combineClassification) { unaryCD[string.Empty] = unaryCD[string.Empty].Plus(localCD); } else { binaryCD.Put(leftCategory, rightCategory, binaryCD.Get(leftCategory, rightCategory).Plus(localCD)); } SimpleMatrix currentVectorDerivative = NeuralUtils.ElementwiseApplyTanhDerivative(currentVector); SimpleMatrix deltaFromClass = model.GetBinaryClassification(leftCategory, rightCategory).Transpose().Mult(deltaClass); deltaFromClass = deltaFromClass.ExtractMatrix(0, model.op.numHid, 0, 1).ElementMult(currentVectorDerivative); SimpleMatrix deltaFull = deltaFromClass.Plus(deltaUp); SimpleMatrix leftVector = RNNCoreAnnotations.GetNodeVector(tree.Children()[0]); SimpleMatrix rightVector = RNNCoreAnnotations.GetNodeVector(tree.Children()[1]); SimpleMatrix childrenVector = NeuralUtils.ConcatenateWithBias(leftVector, rightVector); SimpleMatrix W_df = deltaFull.Mult(childrenVector.Transpose()); binaryTD.Put(leftCategory, rightCategory, binaryTD.Get(leftCategory, rightCategory).Plus(W_df)); SimpleMatrix deltaDown; if (model.op.useTensors) { SimpleTensor Wt_df = GetTensorGradient(deltaFull, leftVector, rightVector); binaryTensorTD.Put(leftCategory, rightCategory, binaryTensorTD.Get(leftCategory, rightCategory).Plus(Wt_df)); deltaDown = ComputeTensorDeltaDown(deltaFull, leftVector, rightVector, model.GetBinaryTransform(leftCategory, rightCategory), model.GetBinaryTensor(leftCategory, rightCategory)); } else { deltaDown = model.GetBinaryTransform(leftCategory, rightCategory).Transpose().Mult(deltaFull); } SimpleMatrix leftDerivative = NeuralUtils.ElementwiseApplyTanhDerivative(leftVector); SimpleMatrix rightDerivative = NeuralUtils.ElementwiseApplyTanhDerivative(rightVector); SimpleMatrix leftDeltaDown = deltaDown.ExtractMatrix(0, deltaFull.NumRows(), 0, 1); SimpleMatrix rightDeltaDown = deltaDown.ExtractMatrix(deltaFull.NumRows(), deltaFull.NumRows() * 2, 0, 1); BackpropDerivativesAndError(tree.Children()[0], binaryTD, binaryCD, binaryTensorTD, unaryCD, wordVectorD, leftDerivative.ElementMult(leftDeltaDown)); BackpropDerivativesAndError(tree.Children()[1], binaryTD, binaryCD, binaryTensorTD, unaryCD, wordVectorD, rightDerivative.ElementMult(rightDeltaDown)); } }
protected internal override void Calculate(double[] theta) { model.VectorToParams(theta); SentimentCostAndGradient.ModelDerivatives derivatives; if (model.op.trainOptions.nThreads == 1) { derivatives = ScoreDerivatives(trainingBatch); } else { // TODO: because some addition operations happen in different // orders now, this results in slightly different values, which // over time add up to significantly different models even when // given the same random seed. Probably not a big deal. // To be more specific, for trees T1, T2, T3, ... Tn, // when using one thread, we sum the derivatives T1 + T2 ... // When using multiple threads, we first sum T1 + ... + Tk, // then sum Tk+1 + ... + T2k, etc, for split size k. // The splits are then summed in order. // This different sum order results in slightly different numbers. MulticoreWrapper <IList <Tree>, SentimentCostAndGradient.ModelDerivatives> wrapper = new MulticoreWrapper <IList <Tree>, SentimentCostAndGradient.ModelDerivatives>(model.op.trainOptions.nThreads, new SentimentCostAndGradient.ScoringProcessor(this )); // use wrapper.nThreads in case the number of threads was automatically changed foreach (IList <Tree> chunk in CollectionUtils.PartitionIntoFolds(trainingBatch, wrapper.NThreads())) { wrapper.Put(chunk); } wrapper.Join(); derivatives = new SentimentCostAndGradient.ModelDerivatives(model); while (wrapper.Peek()) { SentimentCostAndGradient.ModelDerivatives batchDerivatives = wrapper.Poll(); derivatives.Add(batchDerivatives); } } // scale the error by the number of sentences so that the // regularization isn't drowned out for large training batchs double scale = (1.0 / trainingBatch.Count); value = derivatives.error * scale; value += ScaleAndRegularize(derivatives.binaryTD, model.binaryTransform, scale, model.op.trainOptions.regTransformMatrix, false); value += ScaleAndRegularize(derivatives.binaryCD, model.binaryClassification, scale, model.op.trainOptions.regClassification, true); value += ScaleAndRegularizeTensor(derivatives.binaryTensorTD, model.binaryTensors, scale, model.op.trainOptions.regTransformTensor); value += ScaleAndRegularize(derivatives.unaryCD, model.unaryClassification, scale, model.op.trainOptions.regClassification, false, true); value += ScaleAndRegularize(derivatives.wordVectorD, model.wordVectors, scale, model.op.trainOptions.regWordVector, true, false); derivative = NeuralUtils.ParamsToVector(theta.Length, derivatives.binaryTD.ValueIterator(), derivatives.binaryCD.ValueIterator(), SimpleTensor.IteratorSimpleMatrix(derivatives.binaryTensorTD.ValueIterator()), derivatives.unaryCD.Values.GetEnumerator (), derivatives.wordVectorD.Values.GetEnumerator()); }
/// <exception cref="System.IO.IOException"/> public static void Main(string[] args) { string basePath = "/user/socherr/scr/projects/semComp/RNTN/src/params/"; int numSlices = 25; bool useEscapedParens = false; for (int argIndex = 0; argIndex < args.Length;) { if (Sharpen.Runtime.EqualsIgnoreCase(args[argIndex], "-slices")) { numSlices = System.Convert.ToInt32(args[argIndex + 1]); argIndex += 2; } else { if (Sharpen.Runtime.EqualsIgnoreCase(args[argIndex], "-path")) { basePath = args[argIndex + 1]; argIndex += 2; } else { if (Sharpen.Runtime.EqualsIgnoreCase(args[argIndex], "-useEscapedParens")) { useEscapedParens = true; argIndex += 1; } else { log.Info("Unknown argument " + args[argIndex]); System.Environment.Exit(2); } } } } SimpleMatrix[] slices = new SimpleMatrix[numSlices]; for (int i = 0; i < numSlices; ++i) { slices[i] = LoadMatrix(basePath + "bin/Wt_" + (i + 1) + ".bin", basePath + "Wt_" + (i + 1) + ".txt"); } SimpleTensor tensor = new SimpleTensor(slices); log.Info("W tensor size: " + tensor.NumRows() + "x" + tensor.NumCols() + "x" + tensor.NumSlices()); SimpleMatrix W = LoadMatrix(basePath + "bin/W.bin", basePath + "W.txt"); log.Info("W matrix size: " + W.NumRows() + "x" + W.NumCols()); SimpleMatrix Wcat = LoadMatrix(basePath + "bin/Wcat.bin", basePath + "Wcat.txt"); log.Info("W cat size: " + Wcat.NumRows() + "x" + Wcat.NumCols()); SimpleMatrix combinedWV = LoadMatrix(basePath + "bin/Wv.bin", basePath + "Wv.txt"); log.Info("Word matrix size: " + combinedWV.NumRows() + "x" + combinedWV.NumCols()); File vocabFile = new File(basePath + "vocab_1.txt"); if (!vocabFile.Exists()) { vocabFile = new File(basePath + "words.txt"); } IList <string> lines = Generics.NewArrayList(); foreach (string line in IOUtils.ReadLines(vocabFile)) { lines.Add(line.Trim()); } log.Info("Lines in vocab file: " + lines.Count); IDictionary <string, SimpleMatrix> wordVectors = Generics.NewTreeMap(); for (int i_1 = 0; i_1 < lines.Count && i_1 < combinedWV.NumCols(); ++i_1) { string[] pieces = lines[i_1].Split(" +"); if (pieces.Length == 0 || pieces.Length > 1) { continue; } wordVectors[pieces[0]] = combinedWV.ExtractMatrix(0, numSlices, i_1, i_1 + 1); if (pieces[0].Equals("UNK")) { wordVectors[SentimentModel.UnknownWord] = wordVectors["UNK"]; } } // If there is no ",", we first try to look for an HTML escaping, // then fall back to "." as better than just a random word vector. // Same for "``" and ";" CopyWordVector(wordVectors, ",", ","); CopyWordVector(wordVectors, ".", ","); CopyWordVector(wordVectors, ";", ";"); CopyWordVector(wordVectors, ".", ";"); CopyWordVector(wordVectors, "``", "``"); CopyWordVector(wordVectors, "''", "``"); if (useEscapedParens) { ReplaceWordVector(wordVectors, "(", "-LRB-"); ReplaceWordVector(wordVectors, ")", "-RRB-"); } RNNOptions op = new RNNOptions(); op.numHid = numSlices; op.lowercaseWordVectors = false; if (Wcat.NumRows() == 2) { op.classNames = new string[] { "Negative", "Positive" }; op.equivalenceClasses = new int[][] { new int[] { 0 }, new int[] { 1 } }; // TODO: set to null once old models are updated op.numClasses = 2; } if (!wordVectors.Contains(SentimentModel.UnknownWord)) { wordVectors[SentimentModel.UnknownWord] = SimpleMatrix.Random(numSlices, 1, -0.00001, 0.00001, new Random()); } SentimentModel model = SentimentModel.ModelFromMatrices(W, Wcat, tensor, wordVectors, op); model.SaveSerialized("matlab.ser.gz"); }
public bool isMetric(SimpleTensor t) { return(nameManager.isKroneckerOrMetric(t.GetName()) && IndicesUtils.haveEqualStates(t.GetIndices()[0], t.GetIndices()[1])); }
/** * Returns {@code true} if specified tensor is a metric or a Kronecker tensor * * @param t tensor * @return {@code true} if specified tensor is a metric or a Kronecker tensor */ public bool IsKroneckerOrMetric(SimpleTensor t) { return(nameManager.IsKroneckerOrMetric(t.Name)); }
/** * Returns {@code true} if specified tensor is a metric tensor * * @param t tensor * @return {@code true} if specified tensor is a metric tensor */ public bool IsMetric(SimpleTensor t) { return(nameManager.IsKroneckerOrMetric(t.Name) && IndicesUtils.haveEqualStates(t.Indices[0], t.Indices[1])); }