Files
2026-07-13 12:40:42 +08:00

390 lines
12 KiB
C++

// Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved.
//
// 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 <set>
#include <string>
#include <vector>
#include "paddle/phi/core/dense_tensor.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/utils/optional.h"
namespace phi {
struct Segment {
int begin;
int end;
int type;
bool operator==(const Segment& y) const {
return begin == y.begin && end == y.end && type == y.type;
}
};
bool ChunkEnd(int prev_tag,
int prev_type,
int tag,
int type,
int other_chunk_type,
int tag_begin,
int tag_inside,
int tag_end,
int tag_single);
bool ChunkBegin(int prev_tag,
int prev_type,
int tag,
int type,
int other_chunk_type,
int tag_begin,
int tag_inside,
int tag_end,
int tag_single);
void EvalOneSeq(const int64_t* output,
const int64_t* label,
int length,
std::vector<Segment>* output_segments,
std::vector<Segment>* label_segments,
int64_t* num_output_segments,
int64_t* num_label_segments,
int64_t* num_correct,
int num_chunk_types,
int num_tag_types,
int other_chunk_type,
int tag_begin,
int tag_inside,
int tag_end,
int tag_single,
const std::set<int>& excluded_chunk_types);
void GetSegments(const int64_t* label,
int length,
std::vector<Segment>* segments,
int num_chunk_types,
int num_tag_types,
int other_chunk_type,
int tag_begin,
int tag_inside,
int tag_end,
int tag_single) {
segments->clear();
segments->reserve(length);
int chunk_start = 0;
bool in_chunk = false;
int tag = -1;
int type = other_chunk_type;
for (int i = 0; i < length; ++i) {
int prev_tag = tag;
int prev_type = type;
PADDLE_ENFORCE_LE(
label[i],
num_chunk_types * num_tag_types,
common::errors::InvalidArgument(
"The value of Input(Label) should be less than the number of "
"chunk types times the number of tag types, but received %d "
"(Label) vs %d (chunk types) * %d (tag types).",
label[i],
num_chunk_types,
num_tag_types));
tag = label[i] % num_tag_types;
type = label[i] / num_tag_types;
if (in_chunk && ChunkEnd(prev_tag,
prev_type,
tag,
type,
other_chunk_type,
tag_begin,
tag_inside,
tag_end,
tag_single)) {
Segment segment{
chunk_start, // begin
i - 1, // end
prev_type,
};
segments->push_back(segment);
in_chunk = false;
}
if (ChunkBegin(prev_tag,
prev_type,
tag,
type,
other_chunk_type,
tag_begin,
tag_inside,
tag_end,
tag_single)) {
chunk_start = i;
in_chunk = true;
}
}
if (in_chunk) {
Segment segment{
chunk_start, // begin
length - 1, // end
type,
};
segments->push_back(segment);
}
}
bool ChunkEnd(int prev_tag,
int prev_type,
int tag,
int type,
int other_chunk_type,
int tag_begin,
int tag_inside,
int tag_end,
int tag_single) {
if (prev_type == other_chunk_type) return false;
if (type == other_chunk_type) return true;
if (type != prev_type) return true;
if (prev_tag == tag_begin) return tag == tag_begin || tag == tag_single;
if (prev_tag == tag_inside) return tag == tag_begin || tag == tag_single;
if (prev_tag == tag_end) return true;
if (prev_tag == tag_single) return true;
return false;
}
bool ChunkBegin(int prev_tag,
int prev_type,
int tag,
int type,
int other_chunk_type,
int tag_begin,
int tag_inside,
int tag_end,
int tag_single) {
if (prev_type == other_chunk_type) return type != other_chunk_type;
if (type == other_chunk_type) return false;
if (type != prev_type) return true;
if (tag == tag_begin) return true;
if (tag == tag_inside) return prev_tag == tag_end || prev_tag == tag_single;
if (tag == tag_end) return prev_tag == tag_end || prev_tag == tag_single;
if (tag == tag_single) return true;
return false;
}
template <typename T, typename Context>
void ChunkEvalKernel(const Context& dev_ctx,
const DenseTensor& inference,
const DenseTensor& label,
const optional<DenseTensor>& seq_length,
int num_chunk_types,
const std::string& chunk_scheme,
const std::vector<int>& excluded_chunk_types,
DenseTensor* precision,
DenseTensor* recall,
DenseTensor* f1_score,
DenseTensor* num_infer_chunks,
DenseTensor* num_label_chunks,
DenseTensor* num_correct_chunks) {
// initialize to parse configurations
int num_tag_types;
int other_chunk_type;
int tag_begin, tag_inside, tag_end, tag_single;
std::vector<Segment> label_segments;
std::vector<Segment> output_segments;
std::set<int> excluded_chunk_types_new;
if (chunk_scheme == "IOB") {
num_tag_types = 2;
tag_begin = 0;
tag_inside = 1;
tag_end = -1;
tag_single = -1;
} else if (chunk_scheme == "IOE") {
num_tag_types = 2;
tag_begin = -1;
tag_inside = 0;
tag_end = 1;
tag_single = -1;
} else if (chunk_scheme == "IOBES") {
num_tag_types = 4;
tag_begin = 0;
tag_inside = 1;
tag_end = 2;
tag_single = 3;
} else if (chunk_scheme == "plain") {
num_tag_types = 1;
tag_begin = -1;
tag_inside = -1;
tag_end = -1;
tag_single = -1;
} else {
PADDLE_THROW(common::errors::InvalidArgument("Unknown chunk scheme."));
}
other_chunk_type = num_chunk_types;
excluded_chunk_types_new.insert(excluded_chunk_types.begin(),
excluded_chunk_types.end());
const int64_t* inference_data = inference.data<int64_t>();
const int64_t* label_data = label.data<int64_t>();
T* precision_data = dev_ctx.template Alloc<T>(precision);
T* recall_data = dev_ctx.template Alloc<T>(recall);
T* f1_data = dev_ctx.template Alloc<T>(f1_score);
int64_t* num_infer_chunks_data =
dev_ctx.template Alloc<int64_t>(num_infer_chunks);
int64_t* num_label_chunks_data =
dev_ctx.template Alloc<int64_t>(num_label_chunks);
int64_t* num_correct_chunks_data =
dev_ctx.template Alloc<int64_t>(num_correct_chunks);
*num_infer_chunks_data = 0;
*num_label_chunks_data = 0;
*num_correct_chunks_data = 0;
auto lod = label.lod();
bool use_padding = lod.empty();
int num_sequences = 0;
if (use_padding) {
auto dim1 = inference.dims()[1];
auto* seq_length_t = seq_length.get_ptr();
auto* seq_length_data = seq_length_t->data<int64_t>();
num_sequences = seq_length_t->dims()[0];
for (int i = 0; i < num_sequences; ++i) {
int seq_length = seq_length_data[i];
EvalOneSeq(inference_data + i * dim1,
label_data + i * dim1,
seq_length,
&output_segments,
&label_segments,
num_infer_chunks_data,
num_label_chunks_data,
num_correct_chunks_data,
num_chunk_types,
num_tag_types,
other_chunk_type,
tag_begin,
tag_inside,
tag_end,
tag_single,
excluded_chunk_types_new);
}
} else {
PADDLE_ENFORCE_EQ(
lod.size(),
1UL,
common::errors::InvalidArgument(
"Only support one level LoD sequence now, but received %d.",
lod.size()));
PADDLE_ENFORCE_EQ(
lod,
inference.lod(),
common::errors::InvalidArgument(
"Input(Inference) and Input(Label) of Op(chunk_eval) should have "
"same LoD information."));
num_sequences = lod[0].size() - 1;
for (int i = 0; i < num_sequences; ++i) {
int seq_length = lod[0][i + 1] - lod[0][i];
EvalOneSeq(inference_data + lod[0][i],
label_data + lod[0][i],
seq_length,
&output_segments,
&label_segments,
num_infer_chunks_data,
num_label_chunks_data,
num_correct_chunks_data,
num_chunk_types,
num_tag_types,
other_chunk_type,
tag_begin,
tag_inside,
tag_end,
tag_single,
excluded_chunk_types_new);
}
}
*precision_data =
!(*num_infer_chunks_data)
? 0
: static_cast<T>(*num_correct_chunks_data) / (*num_infer_chunks_data);
*recall_data =
!(*num_label_chunks_data)
? 0
: static_cast<T>(*num_correct_chunks_data) / (*num_label_chunks_data);
*f1_data = !(*num_correct_chunks_data)
? 0
: 2 * (*precision_data) * (*recall_data) /
((*precision_data) + (*recall_data));
}
void EvalOneSeq(const int64_t* output,
const int64_t* label,
int length,
std::vector<Segment>* output_segments,
std::vector<Segment>* label_segments,
int64_t* num_output_segments,
int64_t* num_label_segments,
int64_t* num_correct,
int num_chunk_types,
int num_tag_types,
int other_chunk_type,
int tag_begin,
int tag_inside,
int tag_end,
int tag_single,
const std::set<int>& excluded_chunk_types) {
GetSegments(output,
length,
output_segments,
num_chunk_types,
num_tag_types,
other_chunk_type,
tag_begin,
tag_inside,
tag_end,
tag_single);
GetSegments(label,
length,
label_segments,
num_chunk_types,
num_tag_types,
other_chunk_type,
tag_begin,
tag_inside,
tag_end,
tag_single);
size_t i = 0, j = 0;
while (i < output_segments->size() && j < label_segments->size()) {
if (output_segments->at(i) == label_segments->at(j) &&
excluded_chunk_types.count(output_segments->at(i).type) != 1) {
++(*num_correct);
}
if (output_segments->at(i).end < label_segments->at(j).end) {
++i;
} else if (output_segments->at(i).end > label_segments->at(j).end) {
++j;
} else {
++i;
++j;
}
}
for (auto& segment : (*label_segments)) {
if (excluded_chunk_types.count(segment.type) != 1) {
++(*num_label_segments);
}
}
for (auto& segment : (*output_segments)) {
if (excluded_chunk_types.count(segment.type) != 1) {
++(*num_output_segments);
}
}
}
} // namespace phi