parent
8efd08763f
commit
db694172be
@ -0,0 +1,74 @@
|
||||
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. */
|
||||
|
||||
#include "paddle/operators/ctc_edit_distance_op.h"
|
||||
|
||||
namespace paddle {
|
||||
namespace operators {
|
||||
|
||||
class CTCEditDistanceOp : public framework::OperatorWithKernel {
|
||||
public:
|
||||
using framework::OperatorWithKernel::OperatorWithKernel;
|
||||
|
||||
void InferShape(framework::InferShapeContext *ctx) const override {
|
||||
PADDLE_ENFORCE(ctx->HasInput("X1"), "Input(X1) shouldn't be null.");
|
||||
PADDLE_ENFORCE(ctx->HasInput("X2"), "Input(X2) shouldn't be null.");
|
||||
PADDLE_ENFORCE(ctx->HasOutput("Out"), "Output(Out) shouldn't be null.");
|
||||
ctx->SetOutputDim("Out", {1});
|
||||
}
|
||||
};
|
||||
|
||||
class CTCEditDistanceOpMaker : public framework::OpProtoAndCheckerMaker {
|
||||
public:
|
||||
CTCEditDistanceOpMaker(framework::OpProto *proto,
|
||||
framework::OpAttrChecker *op_checker)
|
||||
: OpProtoAndCheckerMaker(proto, op_checker) {
|
||||
AddInput("X1",
|
||||
"(2-D tensor with shape [M x 1]) The indices for "
|
||||
"hypothesis string");
|
||||
AddInput("X2",
|
||||
"(2-D tensor with shape [batch_size x 1]) The indices "
|
||||
"for reference string.");
|
||||
AddAttr<bool>("normalized",
|
||||
"(bool, default false) Indicated whether "
|
||||
"normalize. the Output(Out) by the length of reference "
|
||||
"string (X2).")
|
||||
.SetDefault(false);
|
||||
AddOutput("Out",
|
||||
"(2-D tensor with shape [1 x 1]) "
|
||||
"The output distance of CTCEditDistance operator.");
|
||||
AddComment(R"DOC(
|
||||
|
||||
CTCEditDistance operator computes the edit distance of two sequences, one named
|
||||
hypothesis and another named reference.
|
||||
|
||||
Edit distance measures how dissimilar two strings, one is hypothesis and another
|
||||
is reference, are by counting the minimum number of operations to transform
|
||||
one string into anthor.
|
||||
|
||||
)DOC");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace operators
|
||||
} // namespace paddle
|
||||
|
||||
namespace ops = paddle::operators;
|
||||
|
||||
REGISTER_OP_WITHOUT_GRADIENT(ctc_edit_distance, ops::CTCEditDistanceOp,
|
||||
ops::CTCEditDistanceOpMaker);
|
||||
REGISTER_OP_CPU_KERNEL(
|
||||
ctc_edit_distance,
|
||||
ops::CTCEditDistanceKernel<paddle::platform::CPUPlace, int32_t>,
|
||||
ops::CTCEditDistanceKernel<paddle::platform::CPUPlace, int64_t>);
|
@ -0,0 +1,78 @@
|
||||
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. */
|
||||
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include "paddle/framework/eigen.h"
|
||||
#include "paddle/framework/op_registry.h"
|
||||
|
||||
namespace paddle {
|
||||
namespace operators {
|
||||
|
||||
template <typename Place, typename T>
|
||||
class CTCEditDistanceKernel : public framework::OpKernel<T> {
|
||||
public:
|
||||
void Compute(const framework::ExecutionContext& ctx) const {
|
||||
auto* out_t = ctx.Output<framework::Tensor>("Out");
|
||||
|
||||
auto* x1_t = ctx.Input<framework::Tensor>("X1");
|
||||
auto* x2_t = ctx.Input<framework::Tensor>("X2");
|
||||
|
||||
out_t->mutable_data<float>(ctx.GetPlace());
|
||||
|
||||
auto normalized = ctx.Attr<bool>("normalized");
|
||||
|
||||
auto m = x1_t->numel();
|
||||
auto n = x2_t->numel();
|
||||
float distance = 0.0;
|
||||
if (m == 0) {
|
||||
distance = n;
|
||||
} else if (n == 0) {
|
||||
distance = m;
|
||||
} else {
|
||||
framework::Tensor dist_t;
|
||||
dist_t.Resize({m + 1, n + 1});
|
||||
dist_t.mutable_data<T>(ctx.GetPlace());
|
||||
auto dist = dist_t.data<T>();
|
||||
auto x1 = x1_t->data<T>();
|
||||
auto x2 = x2_t->data<T>();
|
||||
for (int i = 0; i < m + 1; ++i) {
|
||||
dist[i * (n + 1)] = i; // dist[i][0] = i;
|
||||
}
|
||||
for (int j = 0; j < n + 1; ++j) {
|
||||
dist[j] = j; // dist[0][j] = j;
|
||||
}
|
||||
for (int i = 1; i < m + 1; ++i) {
|
||||
for (int j = 1; j < n + 1; ++j) {
|
||||
int cost = x1[i - 1] == x2[j - 1] ? 0 : 1;
|
||||
int deletions = dist[(i - 1) * (n + 1) + j] + 1;
|
||||
int insertions = dist[i * (n + 1) + (j - 1)] + 1;
|
||||
int substitutions = dist[(i - 1) * (n + 1) + (j - 1)] + cost;
|
||||
dist[i * (n + 1) + j] =
|
||||
std::min(deletions, std::min(insertions, substitutions));
|
||||
}
|
||||
}
|
||||
distance = dist[m * (n + 1) + n];
|
||||
}
|
||||
|
||||
if (normalized) {
|
||||
distance = distance / n;
|
||||
}
|
||||
auto out = out_t->data<float>();
|
||||
out[0] = distance;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace operators
|
||||
} // namespace paddle
|
@ -0,0 +1,60 @@
|
||||
import unittest
|
||||
import numpy as np
|
||||
from op_test import OpTest
|
||||
|
||||
|
||||
def Levenshtein(hyp, ref):
|
||||
""" Compute the Levenshtein distance between two strings.
|
||||
|
||||
:param hyp:
|
||||
:type hyp: list
|
||||
:param ref:
|
||||
:type ref: list
|
||||
"""
|
||||
m = len(hyp)
|
||||
n = len(ref)
|
||||
if m == 0:
|
||||
return n
|
||||
if n == 0:
|
||||
return m
|
||||
|
||||
dist = np.zeros((m + 1, n + 1))
|
||||
for i in range(0, m + 1):
|
||||
dist[i][0] = i
|
||||
for j in range(0, n + 1):
|
||||
dist[0][j] = j
|
||||
|
||||
for i in range(1, m + 1):
|
||||
for j in range(1, n + 1):
|
||||
cost = 0 if hyp[i - 1] == ref[j - 1] else 1
|
||||
deletion = dist[i - 1][j] + 1
|
||||
insertion = dist[i][j - 1] + 1
|
||||
substitution = dist[i - 1][j - 1] + cost
|
||||
dist[i][j] = min(deletion, insertion, substitution)
|
||||
return dist[m][n]
|
||||
|
||||
|
||||
class TestCTCEditDistanceOp(OpTest):
|
||||
def setUp(self):
|
||||
self.op_type = "ctc_edit_distance"
|
||||
normalized = True
|
||||
x1 = np.array([0, 12, 3, 5]).astype("int64")
|
||||
x2 = np.array([0, 12, 4, 7, 8]).astype("int64")
|
||||
|
||||
distance = Levenshtein(hyp=x1, ref=x2)
|
||||
if normalized is True:
|
||||
distance = distance / len(x2)
|
||||
print "distance = ", distance
|
||||
self.attrs = {'normalized': normalized}
|
||||
self.inputs = {'X1': x1, 'X2': x2}
|
||||
self.outputs = {'Out': distance}
|
||||
|
||||
def test_check_output(self):
|
||||
self.check_output()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#x1 = ['c', 'a', 'f', 'e']
|
||||
#x2 = ['c', 'o', 'f', 'f', 'e', 'e']
|
||||
#print Levenshtein(x1, x2)
|
||||
unittest.main()
|
Loading…
Reference in new issue