public static void RunEndToEndTest(IList <Tree> binarizedTrees, Oracle oracle) { for (int index = 0; index < binarizedTrees.Count; ++index) { State state = ShiftReduceParser.InitialStateFromGoldTagTree(binarizedTrees[index]); while (!state.IsFinished()) { OracleTransition gold = oracle.GoldTransition(index, state); NUnit.Framework.Assert.IsTrue(gold.transition != null); state = gold.transition.Apply(state); } NUnit.Framework.Assert.AreEqual(binarizedTrees[index], state.stack.Peek()); } }
/// <summary> /// Returns an attempt at a "gold" transition given the current state /// while parsing a known gold tree. /// </summary> /// <remarks> /// Returns an attempt at a "gold" transition given the current state /// while parsing a known gold tree. /// Tree is passed in by index so the oracle can precompute various /// statistics about the tree. /// If we already finalized, then the correct transition is to idle. /// If the stack is empty, shift is the only possible answer. /// If the first item on the stack is a correct span, correctly /// labeled, and it has unaries transitions above it, then if we are /// not doing compound unaries, the next unary up is the correct /// answer. If we are doing compound unaries, and the state does not /// already have a transition, then the correct answer is a compound /// unary transition to the top of the unary chain. /// If the first item is the entire tree, with no remaining unary /// transitions, then we need to finalize. /// If the first item is a correct span, with or without a correct /// label, and there are no unary transitions to be added, then we /// must look at the next parent. If it has the same left side, then /// we return a shift transition. If it has the same right side, /// then we look at the next subtree on the stack (which must exist). /// If it is also correct, then the transition is to combine the two /// subtrees with the correct label and side. /// TODO: suppose the correct label is not either child label and the /// children are binarized states? We should see what the /// debinarizer does in that case. Perhaps a post-processing step /// If the previous stack item is too small, then any binary reduce /// action is legal, with no gold transition. TODO: can this be improved? /// If the previous stack item is too large, perhaps because of /// incorrectly attached PP/SBAR, for example, we still need to /// binary reduce. TODO: is that correct? TODO: we could look back /// further in the stack to find hints at a label that would work /// better, for example /// If the current item is an incorrect span, then look at the /// containing item. If it has the same left side, shift. If it has /// the same right side, binary reduce (producing an exact span if /// possible). If neither edge is correct, then any of shift or /// binary reduce are acceptable, with no gold transition. TODO: can /// this be improved? /// </remarks> internal virtual OracleTransition GoldTransition(int index, State state) { if (state.finished) { return(new OracleTransition(new IdleTransition(), false, false, false)); } if (state.stack.Size() == 0) { return(new OracleTransition(new ShiftTransition(), false, false, false)); } IDictionary <Tree, Tree> parents = parentMaps[index]; Tree gold = binarizedTrees[index]; IList <Tree> leaves = leafLists[index]; Tree S0 = state.stack.Peek(); Tree enclosingS0 = GetEnclosingTree(S0, parents, leaves); OracleTransition result = GetUnaryTransition(S0, enclosingS0, parents, compoundUnaries); if (result != null) { return(result); } // TODO: we could interject that all trees must end with ROOT, for example if (state.tokenPosition >= state.sentence.Count && state.stack.Size() == 1) { return(new OracleTransition(new FinalizeTransition(rootStates), false, false, false)); } if (state.stack.Size() == 1) { return(new OracleTransition(new ShiftTransition(), false, false, false)); } if (SpansEqual(S0, enclosingS0)) { Tree parent = parents[enclosingS0]; // cannot be root while (SpansEqual(parent, enclosingS0)) { // in case we had missed unary transitions enclosingS0 = parent; parent = parents[parent]; } if (parent.Children()[0] == enclosingS0) { // S0 is the left child of the correct tree return(new OracleTransition(new ShiftTransition(), false, false, false)); } // was the second (right) child. there must be something else on the stack... Tree S1 = state.stack.Pop().Peek(); Tree enclosingS1 = GetEnclosingTree(S1, parents, leaves); if (SpansEqual(S1, enclosingS1)) { // the two subtrees should be combined return(new OracleTransition(new BinaryTransition(parent.Value(), ShiftReduceUtils.GetBinarySide(parent)), false, false, false)); } return(new OracleTransition(null, false, true, false)); } if (ShiftReduceUtils.LeftIndex(S0) == ShiftReduceUtils.LeftIndex(enclosingS0)) { return(new OracleTransition(new ShiftTransition(), false, false, false)); } if (ShiftReduceUtils.RightIndex(S0) == ShiftReduceUtils.RightIndex(enclosingS0)) { Tree S1 = state.stack.Pop().Peek(); Tree enclosingS1 = GetEnclosingTree(S1, parents, leaves); if (enclosingS0 == enclosingS1) { // BinaryTransition with enclosingS0's label, either side, but preferring LEFT return(new OracleTransition(new BinaryTransition(enclosingS0.Value(), BinaryTransition.Side.Left), false, false, true)); } // S1 is smaller than the next tree S0 is supposed to be part of, // so we must have a BinaryTransition if (ShiftReduceUtils.LeftIndex(S1) > ShiftReduceUtils.LeftIndex(enclosingS0)) { return(new OracleTransition(null, false, true, true)); } // S1 is larger than the next tree. This is the worst case return(new OracleTransition(null, true, true, true)); } // S0 doesn't match either endpoint of the enclosing tree return(new OracleTransition(null, true, true, true)); }
private Pair <int, int> TrainTree(int index, IList <Tree> binarizedTrees, IList <IList <ITransition> > transitionLists, IList <PerceptronModel.Update> updates, Oracle oracle) { int numCorrect = 0; int numWrong = 0; Tree tree = binarizedTrees[index]; ReorderingOracle reorderer = null; if (op.TrainOptions().trainingMethod == ShiftReduceTrainOptions.TrainingMethod.ReorderOracle || op.TrainOptions().trainingMethod == ShiftReduceTrainOptions.TrainingMethod.ReorderBeam) { reorderer = new ReorderingOracle(op); } // TODO. This training method seems to be working in that it // trains models just like the gold and early termination methods do. // However, it causes the feature space to go crazy. Presumably // leaving out features with low weights or low frequencies would // significantly help with that. Otherwise, not sure how to keep // it under control. if (op.TrainOptions().trainingMethod == ShiftReduceTrainOptions.TrainingMethod.Oracle) { State state = ShiftReduceParser.InitialStateFromGoldTagTree(tree); while (!state.IsFinished()) { IList <string> features = featureFactory.Featurize(state); ScoredObject <int> prediction = FindHighestScoringTransition(state, features, true); if (prediction == null) { throw new AssertionError("Did not find a legal transition"); } int predictedNum = prediction.Object(); ITransition predicted = transitionIndex.Get(predictedNum); OracleTransition gold = oracle.GoldTransition(index, state); if (gold.IsCorrect(predicted)) { numCorrect++; if (gold.transition != null && !gold.transition.Equals(predicted)) { int transitionNum = transitionIndex.IndexOf(gold.transition); if (transitionNum < 0) { // TODO: do we want to add unary transitions which are // only possible when the parser has gone off the rails? continue; } updates.Add(new PerceptronModel.Update(features, transitionNum, -1, learningRate)); } } else { numWrong++; int transitionNum = -1; if (gold.transition != null) { transitionNum = transitionIndex.IndexOf(gold.transition); } // TODO: this can theoretically result in a -1 gold // transition if the transition exists, but is a // CompoundUnaryTransition which only exists because the // parser is wrong. Do we want to add those transitions? updates.Add(new PerceptronModel.Update(features, transitionNum, predictedNum, learningRate)); } state = predicted.Apply(state); } } else { if (op.TrainOptions().trainingMethod == ShiftReduceTrainOptions.TrainingMethod.Beam || op.TrainOptions().trainingMethod == ShiftReduceTrainOptions.TrainingMethod.ReorderBeam) { if (op.TrainOptions().beamSize <= 0) { throw new ArgumentException("Illegal beam size " + op.TrainOptions().beamSize); } IList <ITransition> transitions = Generics.NewLinkedList(transitionLists[index]); PriorityQueue <State> agenda = new PriorityQueue <State>(op.TrainOptions().beamSize + 1, ScoredComparator.AscendingComparator); State goldState = ShiftReduceParser.InitialStateFromGoldTagTree(tree); agenda.Add(goldState); // int transitionCount = 0; while (transitions.Count > 0) { ITransition goldTransition = transitions[0]; ITransition highestScoringTransitionFromGoldState = null; double highestScoreFromGoldState = 0.0; PriorityQueue <State> newAgenda = new PriorityQueue <State>(op.TrainOptions().beamSize + 1, ScoredComparator.AscendingComparator); State highestScoringState = null; State highestCurrentState = null; foreach (State currentState in agenda) { bool isGoldState = (op.TrainOptions().trainingMethod == ShiftReduceTrainOptions.TrainingMethod.ReorderBeam && goldState.AreTransitionsEqual(currentState)); IList <string> features = featureFactory.Featurize(currentState); ICollection <ScoredObject <int> > stateTransitions = FindHighestScoringTransitions(currentState, features, true, op.TrainOptions().beamSize, null); foreach (ScoredObject <int> transition in stateTransitions) { State newState = transitionIndex.Get(transition.Object()).Apply(currentState, transition.Score()); newAgenda.Add(newState); if (newAgenda.Count > op.TrainOptions().beamSize) { newAgenda.Poll(); } if (highestScoringState == null || highestScoringState.Score() < newState.Score()) { highestScoringState = newState; highestCurrentState = currentState; } if (isGoldState && (highestScoringTransitionFromGoldState == null || transition.Score() > highestScoreFromGoldState)) { highestScoringTransitionFromGoldState = transitionIndex.Get(transition.Object()); highestScoreFromGoldState = transition.Score(); } } } // This can happen if the REORDER_BEAM method backs itself // into a corner, such as transitioning to something that // can't have a FinalizeTransition applied. This doesn't // happen for the BEAM method because in that case the correct // state (eg one with ROOT) isn't on the agenda so it stops. if (op.TrainOptions().trainingMethod == ShiftReduceTrainOptions.TrainingMethod.ReorderBeam && highestScoringTransitionFromGoldState == null) { break; } State newGoldState = goldTransition.Apply(goldState, 0.0); // if highest scoring state used the correct transition, no training // otherwise, down the last transition, up the correct if (!newGoldState.AreTransitionsEqual(highestScoringState)) { ++numWrong; IList <string> goldFeatures = featureFactory.Featurize(goldState); int lastTransition = transitionIndex.IndexOf(highestScoringState.transitions.Peek()); updates.Add(new PerceptronModel.Update(featureFactory.Featurize(highestCurrentState), -1, lastTransition, learningRate)); updates.Add(new PerceptronModel.Update(goldFeatures, transitionIndex.IndexOf(goldTransition), -1, learningRate)); if (op.TrainOptions().trainingMethod == ShiftReduceTrainOptions.TrainingMethod.Beam) { // If the correct state has fallen off the agenda, break if (!ShiftReduceUtils.FindStateOnAgenda(newAgenda, newGoldState)) { break; } else { transitions.Remove(0); } } else { if (op.TrainOptions().trainingMethod == ShiftReduceTrainOptions.TrainingMethod.ReorderBeam) { if (!ShiftReduceUtils.FindStateOnAgenda(newAgenda, newGoldState)) { if (!reorderer.Reorder(goldState, highestScoringTransitionFromGoldState, transitions)) { break; } newGoldState = highestScoringTransitionFromGoldState.Apply(goldState); if (!ShiftReduceUtils.FindStateOnAgenda(newAgenda, newGoldState)) { break; } } else { transitions.Remove(0); } } } } else { ++numCorrect; transitions.Remove(0); } goldState = newGoldState; agenda = newAgenda; } } else { if (op.TrainOptions().trainingMethod == ShiftReduceTrainOptions.TrainingMethod.ReorderOracle || op.TrainOptions().trainingMethod == ShiftReduceTrainOptions.TrainingMethod.EarlyTermination || op.TrainOptions().trainingMethod == ShiftReduceTrainOptions.TrainingMethod .Gold) { State state = ShiftReduceParser.InitialStateFromGoldTagTree(tree); IList <ITransition> transitions = transitionLists[index]; transitions = Generics.NewLinkedList(transitions); bool keepGoing = true; while (transitions.Count > 0 && keepGoing) { ITransition transition = transitions[0]; int transitionNum = transitionIndex.IndexOf(transition); IList <string> features = featureFactory.Featurize(state); int predictedNum = FindHighestScoringTransition(state, features, false).Object(); ITransition predicted = transitionIndex.Get(predictedNum); if (transitionNum == predictedNum) { transitions.Remove(0); state = transition.Apply(state); numCorrect++; } else { numWrong++; // TODO: allow weighted features, weighted training, etc updates.Add(new PerceptronModel.Update(features, transitionNum, predictedNum, learningRate)); switch (op.TrainOptions().trainingMethod) { case ShiftReduceTrainOptions.TrainingMethod.EarlyTermination: { keepGoing = false; break; } case ShiftReduceTrainOptions.TrainingMethod.Gold: { transitions.Remove(0); state = transition.Apply(state); break; } case ShiftReduceTrainOptions.TrainingMethod.ReorderOracle: { keepGoing = reorderer.Reorder(state, predicted, transitions); if (keepGoing) { state = predicted.Apply(state); } break; } default: { throw new ArgumentException("Unexpected method " + op.TrainOptions().trainingMethod); } } } } } } } return(Pair.MakePair(numCorrect, numWrong)); }