evaluator.py 8.7 KB
Newer Older
D
dzhwinter 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13
#  Copyright (c) 2018 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.
D
Dong Zhihong 已提交
14
import numpy as np
武毅 已提交
15

16
import layers
17
from framework import Program, unique_name, Variable, program_guard
18
from layer_helper import LayerHelper
武毅 已提交
19

20 21 22 23
__all__ = [
    'Accuracy',
    'ChunkEvaluator',
]
Y
Yu Yang 已提交
24 25 26


def _clone_var_(block, var):
D
Dong Zhihong 已提交
27 28 29 30
    assert isinstance(var, Variable)
    return block.create_var(
        name=var.name,
        shape=var.shape,
F
fengjiayi 已提交
31
        dtype=var.dtype,
D
Dong Zhihong 已提交
32 33 34 35 36
        type=var.type,
        lod_level=var.lod_level,
        persistable=True)


D
Dong Zhihong 已提交
37 38
class Evaluator(object):
    """
Y
Yu Yang 已提交
39
    Base Class for all evaluators
40

Y
Yu Yang 已提交
41
    Args:
42
        name(str): The name of evaluator. such as, "accuracy". Used for generate
Y
Yu Yang 已提交
43
            temporary variable name.
44
        main_program(Program, optional): The evaluator should be added to this
Y
Yu Yang 已提交
45
            main_program. Default default_main_program()
46
        startup_program(Program, optional):The parameter should be added to this
Y
Yu Yang 已提交
47
            startup_program. Default default_startup_program()
48

Y
Yu Yang 已提交
49
    Attributes:
50
        states(list): The list of state variables. states will be reset to zero
Y
Yu Yang 已提交
51
            when `reset` is invoked.
52
        metrics(list): The list of metrics variables. They will be calculate
Y
Yu Yang 已提交
53
            every mini-batch
D
Dong Zhihong 已提交
54
    """
武毅 已提交
55

D
Dong Zhihong 已提交
56
    def __init__(self, name, **kwargs):
Y
Yu Yang 已提交
57 58 59 60 61
        self.states = []
        self.metrics = []
        self.helper = LayerHelper(name, **kwargs)

    def reset(self, executor, reset_program=None):
D
Dong Zhihong 已提交
62
        """
Y
Yu Yang 已提交
63
        reset metric states at the begin of each pass/user specified batch
D
Dong Zhihong 已提交
64
        """
Y
Yu Yang 已提交
65 66 67
        if reset_program is None:
            reset_program = Program()

68 69 70 71 72 73
        with program_guard(main_program=reset_program):
            for var in self.states:
                assert isinstance(var, Variable)
                g_var = _clone_var_(reset_program.current_block(), var)
                layers.fill_constant(
                    shape=g_var.shape, value=0.0, dtype=g_var.dtype, out=g_var)
D
Dong Zhihong 已提交
74

Y
Yu Yang 已提交
75
        executor.run(reset_program)
76

Y
Yu Yang 已提交
77
    def eval(self, executor, eval_program=None):
D
Dong Zhihong 已提交
78
        """
Y
Yu Yang 已提交
79
        Evaluate the statistics merged by multiple mini-batches.
D
Dong Zhihong 已提交
80 81
        """
        raise NotImplementedError()
D
Dong Zhihong 已提交
82

Y
Yu Yang 已提交
83
    def create_state(self, suffix, dtype, shape):
武毅 已提交
84
        """
85 86
        Create state variable.

Y
Yu Yang 已提交
87
        NOTE: It is not a public API.
88

Y
Yu Yang 已提交
89
        Args:
90 91 92
            suffix(str): the state suffix.
            dtype(str|core.DataType): the state data type
            shape(tuple|list): the shape of state
Y
Yu Yang 已提交
93 94

        Returns: State variable
武毅 已提交
95

D
Dong Zhihong 已提交
96
        """
Y
Yu Yang 已提交
97 98 99 100 101 102 103
        state = self.helper.create_variable(
            name="_".join([unique_name(self.helper.name), suffix]),
            persistable=True,
            dtype=dtype,
            shape=shape)
        self.states.append(state)
        return state
D
Dong Zhihong 已提交
104

D
Dong Zhihong 已提交
105 106

class Accuracy(Evaluator):
D
Dong Zhihong 已提交
107
    """
Y
Yu Yang 已提交
108
    Average Accuracy for multiple mini-batches.
D
Dong Zhihong 已提交
109 110
    """

Y
Yu Yang 已提交
111
    def __init__(self, input, label, k=1, **kwargs):
D
Dong Zhihong 已提交
112
        super(Accuracy, self).__init__("accuracy", **kwargs)
Y
Yu Yang 已提交
113 114 115 116 117 118 119 120 121 122
        main_program = self.helper.main_program
        if main_program.current_block().idx != 0:
            raise ValueError("You can only invoke Evaluator in root block")

        self.total = self.create_state(dtype='int64', shape=[1], suffix='total')
        self.correct = self.create_state(
            dtype='int64', shape=[1], suffix='correct')
        total = self.helper.create_tmp_variable(dtype='int')
        correct = self.helper.create_tmp_variable(dtype='int')
        acc = layers.accuracy(
123 124 125 126 127
            input=input, label=label, k=k, total=total, correct=correct)
        total = layers.cast(x=total, dtype='int64')
        correct = layers.cast(x=correct, dtype='int64')
        layers.sums(input=[self.total, total], out=self.total)
        layers.sums(input=[self.correct, correct], out=self.correct)
Y
Yu Yang 已提交
128 129

        self.metrics.append(acc)
D
Dong Zhihong 已提交
130

D
Dong Zhihong 已提交
131
    def eval(self, executor, eval_program=None):
Y
Yu Yang 已提交
132
        if eval_program is None:
D
Dong Zhihong 已提交
133
            eval_program = Program()
Y
Yu Yang 已提交
134
        block = eval_program.current_block()
135 136 137 138 139 140
        with program_guard(main_program=eval_program):
            total = _clone_var_(block, self.total)
            correct = _clone_var_(block, self.correct)
            total = layers.cast(total, dtype='float32')
            correct = layers.cast(correct, dtype='float32')
            out = layers.elementwise_div(x=correct, y=total)
Y
Yu Yang 已提交
141
        return np.array(executor.run(eval_program, fetch_list=[out])[0])
G
guosheng 已提交
142 143 144 145


class ChunkEvaluator(Evaluator):
    """
146 147
    Accumulate counter numbers output by chunk_eval from mini-batches and
    compute the precision recall and F1-score using the accumulated counter
G
guosheng 已提交
148 149 150
    numbers.
    """

151 152 153 154 155 156 157 158
    def __init__(
            self,
            input,
            label,
            chunk_scheme,
            num_chunk_types,
            excluded_chunk_types=None, ):
        super(ChunkEvaluator, self).__init__("chunk_eval")
G
guosheng 已提交
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
        main_program = self.helper.main_program
        if main_program.current_block().idx != 0:
            raise ValueError("You can only invoke Evaluator in root block")

        self.num_infer_chunks = self.create_state(
            dtype='int64', shape=[1], suffix='num_infer_chunks')
        self.num_label_chunks = self.create_state(
            dtype='int64', shape=[1], suffix='num_label_chunks')
        self.num_correct_chunks = self.create_state(
            dtype='int64', shape=[1], suffix='num_correct_chunks')
        precision, recall, f1_score, num_infer_chunks, num_label_chunks, num_correct_chunks = layers.chunk_eval(
            input=input,
            label=label,
            chunk_scheme=chunk_scheme,
            num_chunk_types=num_chunk_types,
174
            excluded_chunk_types=excluded_chunk_types, )
G
guosheng 已提交
175 176
        layers.sums(
            input=[self.num_infer_chunks, num_infer_chunks],
177
            out=self.num_infer_chunks)
G
guosheng 已提交
178 179
        layers.sums(
            input=[self.num_label_chunks, num_label_chunks],
180
            out=self.num_label_chunks)
G
guosheng 已提交
181 182
        layers.sums(
            input=[self.num_correct_chunks, num_correct_chunks],
183
            out=self.num_correct_chunks)
G
guosheng 已提交
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206

        self.metrics.extend([precision, recall, f1_score])

    def eval(self, executor, eval_program=None):
        if eval_program is None:
            eval_program = Program()
        block = eval_program.current_block()
        num_infer_chunks, num_label_chunks, num_correct_chunks = executor.run(
            eval_program,
            fetch_list=[_clone_var_(block, state) for state in self.states])
        num_infer_chunks = num_infer_chunks[0]
        num_label_chunks = num_label_chunks[0]
        num_correct_chunks = num_correct_chunks[0]
        precision = float(
            num_correct_chunks) / num_infer_chunks if num_infer_chunks else 0
        recall = float(
            num_correct_chunks) / num_label_chunks if num_label_chunks else 0
        f1_score = float(2 * precision * recall) / (
            precision + recall) if num_correct_chunks else 0
        return np.array(
            [precision], dtype='float32'), np.array(
                [recall], dtype='float32'), np.array(
                    [f1_score], dtype='float32')
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238


class EditDistance(Evaluator):
    """
    Average edit distance error for multiple mini-batches.
    """

    def __init__(self, input, label, k=1, **kwargs):
        super(EditDistance, self).__init__("edit_distance", **kwargs)
        main_program = self.helper.main_program
        if main_program.current_block().idx != 0:
            raise ValueError("You can only invoke Evaluator in root block")

        self.total_error = self.create_state(
            dtype='int64', shape=[1], suffix='total')
        self.batch_num = 0
        error = layers.edit_distance(input=input, label=label)
        mean_error = layers.mean(input=error)
        layers.sums(input=[self.total_error, mean_error], out=self.total_error)
        self.metrics.append(mean_error)

    def eval(self, executor, eval_program=None):
        self.batch_num += 1
        if eval_program is None:
            eval_program = Program()
        block = eval_program.current_block()
        with program_guard(main_program=eval_program):
            total_error = _clone_var_(block, self.total_error)
            batch_num = layers.fill_constant(
                shape=[1], value=self.batch_num, dtype="float32")
            out = layers.elementwise_div(x=total_error, y=batch_num)
        return np.array(executor.run(eval_program, fetch_list=[out])[0])