Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,8 @@ add_library(thot_lib OBJECT
sw_models/SwDefs.h
sw_models/SymmetrizedAligner.cc
sw_models/SymmetrizedAligner.h
sw_models/SymmetrizedAlignmentModel.cc
sw_models/SymmetrizedAlignmentModel.h
)

target_include_directories(thot_lib PUBLIC
Expand Down
25 changes: 25 additions & 0 deletions src/python_module/module.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "sw_models/NormalSentenceLengthModel.h"
#include "sw_models/SentenceLengthModel.h"
#include "sw_models/SymmetrizedAligner.h"
#include "sw_models/SymmetrizedAlignmentModel.h"

#include <memory>
#include <pybind11/numpy.h>
Expand Down Expand Up @@ -310,6 +311,30 @@ PYBIND11_MODULE(thot, m)
py::arg("inverse_aligner"))
.def_property("heuristic", &SymmetrizedAligner::getHeuristic, &SymmetrizedAligner::setHeuristic);

py::class_<SymmetrizedAlignmentModel, SymmetrizedAligner, std::shared_ptr<SymmetrizedAlignmentModel>>(
alignment, "SymmetrizedAlignmentModel")
.def(py::init<std::shared_ptr<AlignmentModel>, std::shared_ptr<AlignmentModel>>(), py::arg("direct_model"),
py::arg("inverse_model"))
.def_property_readonly("num_sentence_pairs", &SymmetrizedAlignmentModel::numSentencePairs)
.def(
"get_sentence_pair",
[](SymmetrizedAlignmentModel& model, unsigned int n) {
std::vector<std::string> srcSentence, trgSentence;
Count c;
if (model.getSentencePair(n, srcSentence, trgSentence, c) == THOT_ERROR)
throw std::out_of_range("The sentence pair index is out of range.");
return std::make_tuple(srcSentence, trgSentence, (float)c);
},
py::arg("n"))
.def(
"get_training_alignment",
[](SymmetrizedAlignmentModel& model, size_t n) {
WordAlignmentMatrix waMatrix;
LgProb logProb = model.getTrainingAlignment(n, waMatrix);
return std::make_tuple((double)logProb, std::move(waMatrix));
},
py::arg("n"));

py::enum_<AlignmentModelType>(alignment, "AlignmentModelType")
.value("IBM1", AlignmentModelType::Ibm1)
.value("IBM2", AlignmentModelType::Ibm2)
Expand Down
6 changes: 6 additions & 0 deletions src/shared_library/thot.cc
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,12 @@ extern "C"
return alignmentModel->getMaxSentenceLength();
}

unsigned int swAlignModel_getNumSentencePairs(void* swAlignModelHandle)
{
auto alignmentModel = static_cast<AlignmentModel*>(swAlignModelHandle);
return alignmentModel->numSentencePairs();
}

void swAlignModel_setVariationalBayes(void* swAlignModelHandle, bool variationalBayes)
{
auto alignmentModel = static_cast<AlignmentModel*>(swAlignModelHandle);
Expand Down
2 changes: 2 additions & 0 deletions src/shared_library/thot.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ extern "C"

THOT_API unsigned int swAlignModel_getMaxSentenceLength(void* swAlignModelHandle);

THOT_API unsigned int swAlignModel_getNumSentencePairs(void* swAlignModelHandle);

THOT_API void swAlignModel_setVariationalBayes(void* swAlignModelHandle, bool variationalBayes);

THOT_API bool swAlignModel_getVariationalBayes(void* swAlignModelHandle);
Expand Down
22 changes: 14 additions & 8 deletions src/sw_models/SymmetrizedAligner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,33 +53,39 @@ LgProb SymmetrizedAligner::getBestAlignment(const vector<WordIndex>& srcSentence
WordAlignmentMatrix invMatrix;
LgProb invLogProb = inverseAligner->getBestAlignment(trgSentence, srcSentence, invMatrix);
invMatrix.transpose();
applyHeuristic(bestWaMatrix, invMatrix, heuristic);
return max(logProb, invLogProb);
}

void SymmetrizedAligner::applyHeuristic(WordAlignmentMatrix& matrix, const WordAlignmentMatrix& invMatrix,
SymmetrizationHeuristic heuristic)
{
switch (heuristic)
{
case SymmetrizationHeuristic::Union:
bestWaMatrix |= invMatrix;
matrix |= invMatrix;
break;
case SymmetrizationHeuristic::Intersection:
bestWaMatrix &= invMatrix;
matrix &= invMatrix;
break;
case SymmetrizationHeuristic::Och:
bestWaMatrix.symmetr1(invMatrix);
matrix.symmetr1(invMatrix);
break;
case SymmetrizationHeuristic::Grow:
bestWaMatrix.grow(invMatrix);
matrix.grow(invMatrix);
break;
case SymmetrizationHeuristic::GrowDiag:
bestWaMatrix.growDiag(invMatrix);
matrix.growDiag(invMatrix);
break;
case SymmetrizationHeuristic::GrowDiagFinal:
bestWaMatrix.growDiagFinal(invMatrix);
matrix.growDiagFinal(invMatrix);
break;
case SymmetrizationHeuristic::GrowDiagFinalAnd:
bestWaMatrix.growDiagFinalAnd(invMatrix);
matrix.growDiagFinalAnd(invMatrix);
break;
case SymmetrizationHeuristic::None:
break;
}
return max(logProb, invLogProb);
}

WordIndex SymmetrizedAligner::stringToSrcWordIndex(string s) const
Expand Down
8 changes: 8 additions & 0 deletions src/sw_models/SymmetrizedAligner.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#pragma once

#include "sw_models/Aligner.h"

#include <memory>
Expand Down Expand Up @@ -38,6 +40,12 @@ class SymmetrizedAligner : public virtual Aligner
{
}

protected:
// Combines 'matrix' (direct, src x trg) with 'invMatrix' (inverse, already
// transposed to src x trg) in place according to the given heuristic.
static void applyHeuristic(WordAlignmentMatrix& matrix, const WordAlignmentMatrix& invMatrix,
SymmetrizationHeuristic heuristic);

private:
std::shared_ptr<Aligner> directAligner;
std::shared_ptr<Aligner> inverseAligner;
Expand Down
49 changes: 49 additions & 0 deletions src/sw_models/SymmetrizedAlignmentModel.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#include "sw_models/SymmetrizedAlignmentModel.h"

using namespace std;

SymmetrizedAlignmentModel::SymmetrizedAlignmentModel(shared_ptr<AlignmentModel> directModel,
shared_ptr<AlignmentModel> inverseModel)
: SymmetrizedAligner{directModel, inverseModel}, directModel{directModel}, inverseModel{inverseModel}
{
}

unsigned int SymmetrizedAlignmentModel::numSentencePairs()
{
return directModel->numSentencePairs();
}

int SymmetrizedAlignmentModel::getSentencePair(unsigned int n, vector<string>& srcSentStr, vector<string>& trgSentStr,
Count& c)
{
return directModel->getSentencePair(n, srcSentStr, trgSentStr, c);
}

LgProb SymmetrizedAlignmentModel::getTrainingAlignment(size_t n, WordAlignmentMatrix& bestWaMatrix)
{
LgProb logProb = directModel->getTrainingAlignment(n, bestWaMatrix);
if (getHeuristic() == SymmetrizationHeuristic::None)
return logProb;

WordAlignmentMatrix invMatrix;
LgProb invLogProb = inverseModel->getTrainingAlignment(n, invMatrix);
invMatrix.transpose();

// Skip the combine when the matrices are degenerate or their dimensions don't
// line up (e.g. an out-of-range n, or a pair filtered out of training in only
// one direction): the heuristic operations require matching dimensions.
if (bestWaMatrix.get_I() == 0 || bestWaMatrix.get_J() == 0 || invMatrix.get_I() != bestWaMatrix.get_I() ||
invMatrix.get_J() != bestWaMatrix.get_J())
return logProb;

applyHeuristic(bestWaMatrix, invMatrix, getHeuristic());
return max(logProb, invLogProb);
}

LgProb SymmetrizedAlignmentModel::getTrainingAlignment(size_t n, vector<PositionIndex>& alignment)
{
WordAlignmentMatrix waMatrix;
LgProb logProb = getTrainingAlignment(n, waMatrix);
waMatrix.getAligVec(alignment);
return logProb;
}
48 changes: 48 additions & 0 deletions src/sw_models/SymmetrizedAlignmentModel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#pragma once

#include "nlp_common/Count.h"
#include "sw_models/AlignmentModel.h"
#include "sw_models/SymmetrizedAligner.h"

#include <memory>

// A symmetrized aligner that also supports transductive alignment, i.e.
// getTrainingAlignment. It combines the per-training-pair alignments inferred by
// a direct model (trained on src->trg) and an inverse model (trained on the same
// pairs with trg/src swapped) using a symmetrization heuristic.
//
// Both models must be trained on the same sentence pairs in the same order (the
// inverse with source and target swapped) and with setEmitTrainingAlignments(true),
// so that index n lines up across the two corpora.
//
// Inherits the symmetrized getBestAlignment overloads and the 'heuristic'
// property from SymmetrizedAligner.
class SymmetrizedAlignmentModel : public SymmetrizedAligner
{
public:
SymmetrizedAlignmentModel(std::shared_ptr<AlignmentModel> directModel,
std::shared_ptr<AlignmentModel> inverseModel);

unsigned int numSentencePairs();

// Returns the source/target sentences (and count) for training pair n, in
// src->trg order (delegates to the direct model).
int getSentencePair(unsigned int n, std::vector<std::string>& srcSentStr, std::vector<std::string>& trgSentStr,
Count& c);

// The symmetrized alignment for training pair n, mirroring getBestAlignment:
// combines the direct and inverse models' training alignments under the current
// heuristic. Fills 'alignment' (per target token, 1-based source position,
// 0 = NULL) and returns the direction-max log-probability.
LgProb getTrainingAlignment(size_t n, std::vector<PositionIndex>& alignment);
// As above, but fills a WordAlignmentMatrix instead of a per-target vector.
LgProb getTrainingAlignment(size_t n, WordAlignmentMatrix& bestWaMatrix);

virtual ~SymmetrizedAlignmentModel()
{
}

private:
std::shared_ptr<AlignmentModel> directModel;
std::shared_ptr<AlignmentModel> inverseModel;
};
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_executable(thot_test
sw_models/IncrHmmAlignmentModelTest.cc
sw_models/LexTableTest.h
sw_models/MemoryLexTableTest.cc
sw_models/SymmetrizedAlignmentModelTest.cc
sw_models/TestUtils.cc
sw_models/TestUtils.h
)
Expand Down
141 changes: 141 additions & 0 deletions tests/sw_models/SymmetrizedAlignmentModelTest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#include "sw_models/SymmetrizedAlignmentModel.h"

#include "TestUtils.h"
#include "nlp_common/ErrorDefs.h"
#include "nlp_common/MathDefs.h"
#include "sw_models/FastAlignModel.h"

#include <gtest/gtest.h>

#include <memory>

using namespace std;

namespace
{
// Builds a direct model (trained on the test corpus) and an inverse model trained
// on the same pairs with source/target swapped, both emitting training
// alignments, then wraps them in a SymmetrizedAlignmentModel.
shared_ptr<SymmetrizedAlignmentModel> buildSymmetrizedModel(shared_ptr<FastAlignModel>& direct,
shared_ptr<FastAlignModel>& inverse)
{
direct = make_shared<FastAlignModel>();
direct->setEmitTrainingAlignments(true);
addTrainingData(*direct);
train(*direct, 2);

inverse = make_shared<FastAlignModel>();
inverse->setEmitTrainingAlignments(true);
for (unsigned int n = 0; n < direct->numSentencePairs(); ++n)
{
vector<string> src, trg;
Count c;
direct->getSentencePair(n, src, trg, c);
inverse->addSentencePair(trg, src, c); // swapped
}
train(*inverse, 2);

return make_shared<SymmetrizedAlignmentModel>(direct, inverse);
}
} // namespace

TEST(SymmetrizedAlignmentModelTest, sentencePairAccessors)
{
shared_ptr<FastAlignModel> direct, inverse;
shared_ptr<SymmetrizedAlignmentModel> model = buildSymmetrizedModel(direct, inverse);

EXPECT_EQ(model->numSentencePairs(), direct->numSentencePairs());

// get_sentence_pair returns the direct model's pairs, in src->trg order (not
// the swapped inverse order).
for (unsigned int n = 0; n < model->numSentencePairs(); ++n)
{
vector<string> src, trg, expectedSrc, expectedTrg;
Count c, expectedC;
direct->getSentencePair(n, expectedSrc, expectedTrg, expectedC);
model->getSentencePair(n, src, trg, c);
EXPECT_EQ(src, expectedSrc);
EXPECT_EQ(trg, expectedTrg);
}
}

TEST(SymmetrizedAlignmentModelTest, heuristicNoneReturnsDirect)
{
shared_ptr<FastAlignModel> direct, inverse;
shared_ptr<SymmetrizedAlignmentModel> model = buildSymmetrizedModel(direct, inverse);
model->setHeuristic(SymmetrizationHeuristic::None);

for (unsigned int n = 0; n < model->numSentencePairs(); ++n)
{
vector<PositionIndex> expected;
LgProb expectedProb = direct->getTrainingAlignment(n, expected);

vector<PositionIndex> alig;
LgProb prob = model->getTrainingAlignment(n, alig);
EXPECT_EQ(alig, expected);
EXPECT_NEAR((double)prob, (double)expectedProb, EPSILON);
}
}

TEST(SymmetrizedAlignmentModelTest, combinesDirectAndInverse)
{
shared_ptr<FastAlignModel> direct, inverse;
shared_ptr<SymmetrizedAlignmentModel> model = buildSymmetrizedModel(direct, inverse);

for (SymmetrizationHeuristic h :
{SymmetrizationHeuristic::Union, SymmetrizationHeuristic::Intersection, SymmetrizationHeuristic::Och,
SymmetrizationHeuristic::GrowDiagFinalAnd})
{
model->setHeuristic(h);
for (unsigned int n = 0; n < model->numSentencePairs(); ++n)
{
// Reconstruct the expected combination directly from the two underlying
// training alignments: direct (src x trg) and inverse transposed (src x trg).
WordAlignmentMatrix expected;
LgProb directProb = direct->getTrainingAlignment(n, expected);
WordAlignmentMatrix invMatrix;
LgProb invProb = inverse->getTrainingAlignment(n, invMatrix);
invMatrix.transpose();
switch (h)
{
case SymmetrizationHeuristic::Union:
expected |= invMatrix;
break;
case SymmetrizationHeuristic::Intersection:
expected &= invMatrix;
break;
case SymmetrizationHeuristic::Och:
expected.symmetr1(invMatrix);
break;
case SymmetrizationHeuristic::GrowDiagFinalAnd:
expected.growDiagFinalAnd(invMatrix);
break;
default:
break;
}

WordAlignmentMatrix actual;
LgProb prob = model->getTrainingAlignment(n, actual);
EXPECT_TRUE(actual == expected);
EXPECT_NEAR((double)prob, (double)max(directProb, invProb), EPSILON);

// The per-target-vector overload agrees with the matrix overload.
vector<PositionIndex> aligVec, expectedVec;
model->getTrainingAlignment(n, aligVec);
expected.getAligVec(expectedVec);
EXPECT_EQ(aligVec, expectedVec);
}
}
}

TEST(SymmetrizedAlignmentModelTest, outOfRangeIndex)
{
shared_ptr<FastAlignModel> direct, inverse;
shared_ptr<SymmetrizedAlignmentModel> model = buildSymmetrizedModel(direct, inverse);
model->setHeuristic(SymmetrizationHeuristic::GrowDiagFinalAnd);

vector<PositionIndex> oob{1, 2, 3};
LgProb prob = model->getTrainingAlignment(1000, oob);
EXPECT_TRUE(oob.empty());
EXPECT_EQ((double)prob, (double)SMALL_LG_NUM);
}
Loading
Loading