tf.one_hot编码的用法以及one-hot自己编码

本文介绍了如何使用TensorFlow的tf.one_hot函数进行one-hot编码,包括参数解析和注意事项,并提供了一个使用numpy自定义编码的例子。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

tf.one_hot编码的用法

最近想将之前用普通机器学习做的一个项目,用三层的全连接神经网络跑一遍,在做label时候,需要将(0,1)这两种label转为one-hot编码,因此探究了一下tf.one_hot的用法。

tf.one_hot(indices, depth, on_value=None, off_value=None, axis=None, dtype=None, name=None)

indices: 是一个矩阵,可以是一维的矩阵(居多),也可以是多维的

depth:要输出的one-hot编码的tensor的列的维度,比如说输入的是(1800,)的一维矩阵,depth是3,其会输出[1800,3]的shape的tensor

on_value:默认是1,可以自己指定,就是将one-hot编码的那个‘1’换成了你所指定的数字

off_value:默认是0,可以自己指定,就是将one-hot编码的那个‘0’换成了你所指定的数字

axis:默认是-1,一般我们就默认好了,符合直觉,具体用法:

If indices is a vector of length features(看作是样本个数,就是上面的1800), the output shape will be:
features x depth if axis == -1
depth x features if axis == 0
If indices is a matrix (batch) with shape [batch, features](有不同组的样本), the output shape will be:
batch x features x depth if axis == -1
batch x depth x features if axis == 1
depth x batch x features if axis == 0

dtype:默认是tf.float32

官方提供的例子:

   indices = [0, 1, 2]
   depth = 3
   tf.one_hot(indices, depth)  # output: [3 x 3]
   # [[1., 0., 0.],
   #  [0., 1., 0.],
   #  [0., 0., 1.]]
   
   indices = [0, 2, -1, 1]
   depth = 3
   tf.one_hot(indices, depth,
              on_value=5.0, off_value=0.0,
              axis=-1)  # output: [4 x 3]
   # [[5.0, 0.0, 0.0],  # one_hot(0)
   #  [0.0, 0.0, 5.0],  # one_hot(2)
   #  [0.0, 0.0, 0.0],  # one_hot(-1)
   #  [0.0, 5.0, 0.0]]  # one_hot(1)
   
   indices = [[0, 2], [1, -1]]
   depth = 3
   tf.one_hot(indices, depth,
              on_value=1.0, off_value=0.0,
              axis=-1)  # output: [2 x 2 x 3]
   # [[[1.0, 0.0, 0.0],   # one_hot(0)
   #   [0.0, 0.0, 1.0]],  # one_hot(2)
   #  [[0.0, 1.0, 0.0],   # one_hot(1)
   #   [0.0, 0.0, 0.0]]]  # one_hot(-1)

自己试验一下发现,这个one-hot编码是从0开始的编码,即使你的label里面没有0,比如说是从(1,2)的label,那它也只会从0开始编码,这个需要特别注意到:

>>> indices = [1,2,1,1
class ClassSpecificPrecision(tf.keras.metrics.Metric): def __init__(self, class_id, num_classes=8, name="class_precision", **kwargs): """ 为指定类别计算精确率 参数: class_id: 要计算精确率的类别ID (0-7) num_classes: 总类别数 (默认为8) name: 指标名称 """ super().__init__(name=name, **kwargs) self.class_id = class_id self.num_classes = num_classes # 初始化真正例(TP)和预测正例(PP)计数器 self.true_positives = self.add_weight( name="tp", initializer="zeros", dtype=tf.float32) self.predicted_positives = self.add_weight( name="pp", initializer="zeros", dtype=tf.float32) def update_state(self, y_true, y_pred, sample_weight=None): # 将标签转换为整数类型 y_true = tf.cast(y_true, tf.int32) # 获取预测类别 (形状: [batch_size]) y_pred_class = tf.argmax(y_pred, axis=-1, output_type=tf.int32) # 计算真正例 (TP): 实际为class_id且预测为class_id true_positive = tf.logical_and( tf.equal(y_true, self.class_id), tf.equal(y_pred_class, self.class_id) ) # 计算预测正例 (PP): 预测为class_id (不论实际类别) predicted_positive = tf.equal(y_pred_class, self.class_id) # 应用样本权重 (如果提供) if sample_weight is not None: true_positive = tf.cast(true_positive, tf.float32) * sample_weight predicted_positive = tf.cast(predicted_positive, tf.float32) * sample_weight else: true_positive = tf.cast(true_positive, tf.float32) predicted_positive = tf.cast(predicted_positive, tf.float32) # 更新状态变量 self.true_positives.assign_add(tf.reduce_sum(true_positive)) self.predicted_positives.assign_add(tf.reduce_sum(predicted_positive)) def result(self): # 避免除以零错误 return tf.where( self.predicted_positives > 0, self.true_positives / self.predicted_positives, 0.0 ) def reset_state(self): # 重置计数器 self.true_positives.assign(0.0) self.predicted_positives.assign(0.0) 上面自定义的指标输出为什么会超过100.0
最新发布
07-26
<think>我们正在讨论一个自定义的Keras指标`ClassSpecificPrecision`,它返回的值超过了100.0(即大于1.0)。根据自定义指标的定义,精确率(Precision)应该是真正例(TP)除以预测正例(TP+FP),其值应该在01之间(或0%到100%)。因此,超过1.0的值是不合理的。 根据引用内容,我们并没有直接关于这个问题的描述,但是我们可以根据指标计算的一般原则和TensorFlow/Keras的工作机制来分析。 可能的原因: 1. 在计算精确率时,分母(预测正例的数量)为零,此时如果分子(真正例)不为零,则会出现无穷大。但在自定义指标中,如果没有正确处理分母为零的情况,可能会导致NaN或Inf,但这里用户报告的是超过100.0,所以可能是分母为零时没有处理,而分子为正数,导致除法结果为无穷大(但在TensorFlow中,除以0会得到inf,而inf在显示时可能被表示为很大的数,比如超过100.0)。 2. 状态变量(如真正例和预测正例的累加器)在更新时出现了错误,导致分子大于分母。例如,可能错误地将其他类别的样本也计入了分子或分母。 3. 样本权重处理不当:如果使用了样本权重,并且权重值大于1,同时更新状态变量时没有正确归一化,也可能导致累加值过大。 4. 数值不稳定:由于浮点计算误差,可能导致一个略大于1的值。 5. 在多个批次中,状态变量没有正确重置,导致跨多个epoch的累积错误。 6. 指标计算逻辑错误:例如,错误地将其他类别的样本也计入了当前类别的统计中。 针对用户提供的自定义指标类,我们重点分析其`update_state`和`result`方法。 原始代码片段: ```python class ClassSpecificPrecision(tf.keras.metrics.Metric): def __init__(self, class_id, num_classes=8, name="class_precision", **kwargs): super().__init__(name=name, **kwargs) # ... 初始化状态变量 ``` 由于用户没有给出完整的实现,我们假设一个常见的实现方式,并指出可能的问题点。 一个典型的实现可能如下(有问题的版本): ```python class ClassSpecificPrecision(tf.keras.metrics.Metric): def __init__(self, class_id, num_classes=8, name="class_precision", **kwargs): super().__init__(name=name, **kwargs) self.class_id = class_id self.true_positives = self.add_weight(name='tp', initializer='zeros') self.false_positives = self.add_weight(name='fp', initializer='zeros') def update_state(self, y_true, y_pred, sample_weight=None): # 将y_true转换为one-hot编码(如果y_true是整数标签) y_true = tf.one_hot(tf.cast(y_true, tf.int32), depth=self.num_classes) # 或者直接使用索引(假设y_true是整数标签,形状为[batch_size]) # 我们关注的是特定类别class_id y_true_class = y_true[..., self.class_id] # 预测值通常是logits或softmax输出,我们取最大概率的类别作为预测类别 y_pred_class = tf.argmax(y_pred, axis=-1) # 或者,我们也可以直接使用预测概率中class_id的概率值,但这里我们使用类别标签 # 计算真正例:实际是class_id且预测也是class_id true_positive = tf.logical_and(tf.equal(y_true_class, 1), tf.equal(y_pred_class, self.class_id)) true_positive = tf.cast(true_positive, tf.float32) # 计算假正例:实际不是class_id但预测是class_id false_positive = tf.logical_and(tf.not_equal(y_true_class, 1), tf.equal(y_pred_class, self.class_id)) false_positive = tf.cast(false_positive, tf.float32) # 如果有样本权重,则应用 if sample_weight is not None: sample_weight = tf.cast(sample_weight, tf.float32) true_positive = true_positive * sample_weight false_positive = false_positive * sample_weight # 更新状态变量 self.true_positives.assign_add(tf.reduce_sum(true_positive)) self.false_positives.assign_add(tf.reduce_sum(false_positive)) def result(self): # 精确率 = TP / (TP + FP) return self.true_positives / (self.true_positives + self.false_positives) ``` 上述实现中,如果`(self.true_positives + self.false_positives)`为0,则会出现除以0的情况。在TensorFlow中,除以0会得到`inf`(无穷大),这可能是导致结果超过100.0的原因。 修正方法:在`result`方法中,当分母为0时,返回0(或其他默认值,如0或NaN,但通常返回0更合理)。 修改后的`result`方法: ```python def result(self): tp_plus_fp = self.true_positives + self.false_positives # 使用tf.cond处理分母为0的情况 return tf.cond(tf.greater(tp_plus_fp, 0), lambda: self.true_positives / tp_plus_fp, lambda: tf.constant(0.0, dtype=tf.float32)) ``` 另外,还有一个潜在问题:在`update_state`中,我们假设`y_true`是整数标签,但我们又尝试将其作为one-hot编码处理。实际上,如果`y_true`已经是one-hot编码,那么`y_true[..., self.class_id]`就是正确的。但如果`y_true`是整数标签,则应该先将其转换为one-hot编码,或者直接使用整数标签进行比较。 更合理的做法是同时支持整数标签和one-hot编码的`y_true`。但为了简化,我们可以要求`y_true`是整数标签(即形状为[batch_size]),然后直接比较。 修正后的`update_state`(针对整数标签): ```python def update_state(self, y_true, y_pred, sample_weight=None): # 确保y_true是整数类型 y_true = tf.cast(y_true, tf.int32) # 获取预测类别(形状[batch_size]) y_pred_class = tf.argmax(y_pred, axis=-1, output_type=tf.int32) # 计算真正例:实际是class_id且预测也是class_id true_positive = tf.logical_and(tf.equal(y_true, self.class_id), tf.equal(y_pred_class, self.class_id)) true_positive = tf.cast(true_positive, tf.float32) # 计算假正例:实际不是class_id但预测是class_id false_positive = tf.logical_and(tf.not_equal(y_true, self.class_id), tf.equal(y_pred_class, self.class_id)) false_positive = tf.cast(false_positive, tf.float32) # ... 样本权重处理同上 ``` 但是,注意:在二分类或多分类中,我们通常将每个类别独立计算精确率。这里我们只计算特定类别的精确率。 另一个可能的问题是:在计算假正例时,我们使用了`tf.not_equal(y_true, self.class_id)`,这包括了所有其他类别。这符合定义。 然而,还有一种情况:如果样本权重被应用,并且权重值很大,那么累加的TP和FP可能会很大,导致在计算精确率时分子大于分母?不会,因为精确率是TP/(TP+FP),分子TP<=TP+FP(因为FP>=0),所以精确率不会超过1。除非在累加状态变量时出现了错误,比如将整个batch的样本数累加到了TP中,而实际上TP只是其中一部分。 因此,最可能的原因就是分母为零的情况没有处理。 此外,还有一点:在初始化时,用户指定了`num_classes=8`,但在类内部并没有使用(除了可能在one-hot编码时)。如果用户提供的`y_true`是整数标签,且标签值可能超过7(因为num_classes=8,索引0-7),那么使用`tf.one_hot(..., depth=8)`是安全的。但如果没有使用one-hot,而是直接比较整数,则不需要num_classes。 建议:在`__init__`中,我们不需要`num_classes`,除非我们要使用one-hot编码。所以我们可以移除它,或者保留但确保在计算中使用。 总结解决方案: 1. 在`result`方法中处理分母为零的情况。 2. 确保标签比较正确(整数标签直接比较,不要错误使用one-hot编码)。 3. 检查样本权重的处理,确保权重不会导致状态变量异常增长。 修正后的完整类: ```python class ClassSpecificPrecision(tf.keras.metrics.Metric): def __init__(self, class_id, name="class_precision", **kwargs): super().__init__(name=name, **kwargs) self.class_id = class_id self.true_positives = self.add_weight(name='tp', initializer='zeros') self.false_positives = self.add_weight(name='fp', initializer='zeros') def update_state(self, y_true, y_pred, sample_weight=None): # 将y_true转换为整数类型(如果还不是) y_true = tf.cast(y_true, tf.int32) # 获取预测类别(整数) y_pred_class = tf.argmax(y_pred, axis=-1, output_type=tf.int32) # 计算真正例:实际为class_id且预测为class_id tp = tf.equal(y_true, self.class_id) & tf.equal(y_pred_class, self.class_id) tp = tf.cast(tp, tf.float32) # 计算假正例:实际不是class_id但预测为class_id fp = tf.not_equal(y_true, self.class_id) & tf.equal(y_pred_class, self.class_id) fp = tf.cast(fp, tf.float32) # 应用样本权重 if sample_weight is not None: sample_weight = tf.cast(sample_weight, tf.float32) tp = tp * sample_weight fp = fp * sample_weight # 更新状态变量 self.true_positives.assign_add(tf.reduce_sum(tp)) self.false_positives.assign_add(tf.reduce_sum(fp)) def result(self): tp_plus_fp = self.true_positives + self.false_positives # 当分母为0时,返回0.0,避免除以0 return tf.cond( tf.greater(tp_plus_fp, 0), lambda: self.true_positives / tp_plus_fp, lambda: 0.0 ) def reset_states(self): self.true_positives.assign(0) self.false_positives.assign(0) ``` 注意:我们添加了`reset_states`方法,用于在每次epoch开始时重置状态。 这样,当没有预测为该类别的样本时(即分母为0),我们返回0.0。这符合通常的理解:没有预测正例,则精确率定义为0(或undefined,但0更便于计算)。 此外,我们也可以考虑使用`tf.where`来处理分母为零的情况,但`tf.cond`在这里更合适,因为分母是一个标量(状态变量是标量)。 最后,用户应该检查在训练过程中是否在每个epoch开始时正确重置了指标状态。在Keras中,如果使用`model.fit`,指标会在每个epoch开始时自动重置(通过回调机制,如引用[2]所述)。但自定义指标需要实现`reset_states`方法。 如果用户仍然遇到问题,可以添加调试打印语句,查看每次更新后TP和FP的值,以及分母的值。 因此,主要原因是分母为零未处理,修正方法是添加条件判断。 另外,如果用户看到精确率大于1,也可能是由于其他错误(如将整个batch的样本数累加到了TP中),但根据上述实现,这种情况不太可能发生。 下面我们提供完整的修正代码,并确保处理了分母为零的情况。</think># Keras自定义类别精确率指标超过100%的原因分析与解决 根据您提供的自定义指标代码和需求,我将分析为什么`ClassSpecificPrecision`指标可能返回超过100%的值,并提供完整的解决方案。 ## 问题原因分析 ### 1. 分母为零未处理(最常见原因) 精确率计算公式为: $$ \text{Precision} = \frac{\text{TP}}{\text{TP} + \text{FP}} $$ 当分母(TP + FP)为零时(即没有预测为该类别的样本),如果分子(TP)不为零,会导致无限大的结果[^1]。 ### 2. 状态变量累积错误 在自定义指标中,如果状态变量更新逻辑有误,可能导致: ```python self.true_positives.assign_add(tp) # 正确累积TP self.predicted_positives.assign_add(pp) # 错误地将FP累积为PP ``` ### 3. 数值精度问题 浮点运算中的舍入误差可能导致: $$ \frac{100.0}{99.999} \approx 100.001\% $$ ### 4. 样本权重处理不当 如果使用了样本权重但处理不正确,可能使权重值超过1,导致计算结果异常。 ## 完整解决方案 ### 修正后的类别特定精确率指标实现 ```python import tensorflow as tf from tensorflow.keras import metrics class ClassSpecificPrecision(metrics.Metric): def __init__(self, class_id, name=None, dtype=tf.float32): super().__init__(name=name, dtype=dtype) self.class_id = class_id self.true_positives = self.add_weight("tp", initializer="zeros") self.predicted_positives = self.add_weight("pp", initializer="zeros") def update_state(self, y_true, y_pred, sample_weight=None): # 确保输入为整数类型 y_true = tf.cast(y_true, tf.int32) # 获取预测类别(argmax) y_pred_class = tf.argmax(y_pred, axis=-1, output_type=tf.int32) # 创建当前类别的掩码 true_mask = tf.equal(y_true, self.class_id) pred_mask = tf.equal(y_pred_class, self.class_id) # 计算真正例(TP)和预测正例(PP) true_positives = tf.logical_and(true_mask, pred_mask) predicted_positives = pred_mask # 处理样本权重 if sample_weight is not None: sample_weight = tf.cast(sample_weight, self.dtype) true_positives = tf.cast(true_positives, self.dtype) * sample_weight predicted_positives = tf.cast(predicted_positives, self.dtype) * sample_weight else: true_positives = tf.cast(true_positives, self.dtype) predicted_positives = tf.cast(predicted_positives, self.dtype) # 累积状态变量 self.true_positives.assign_add(tf.reduce_sum(true_positives)) self.predicted_positives.assign_add(tf.reduce_sum(predicted_positives)) def result(self): # 防止分母为零 precision = tf.cond( tf.greater(self.predicted_positives, 0), lambda: self.true_positives / self.predicted_positives, lambda: 0.0 ) # 确保结果在[0, 1]范围内 return tf.clip_by_value(precision, 0.0, 1.0) def reset_states(self): self.true_positives.assign(0) self.predicted_positives.assign(0) ``` ### 关键改进点 1. **分母为零保护**: ```python precision = tf.cond( tf.greater(self.predicted_positives, 0), lambda: self.true_positives / self.predicted_positives, lambda: 0.0 ) ``` 2. **结果范围限制**: ```python return tf.clip_by_value(precision, 0.0, 1.0) ``` 3. **类型安全转换**: ```python y_true = tf.cast(y_true, tf.int32) y_pred_class = tf.argmax(y_pred, axis=-1, output_type=tf.int32) ``` 4. **样本权重处理**: ```python if sample_weight is not None: sample_weight = tf.cast(sample_weight, self.dtype) true_positives = tf.cast(true_positives, self.dtype) * sample_weight ``` ## 调试建议 ### 1. 添加调试输出 在`update_state`方法中添加临时打印语句: ```python def update_state(self, y_true, y_pred, sample_weight=None): # ... [前面的代码] # 调试输出 tf.print("Class", self.class_id, "| TP:", tf.reduce_sum(true_positives), "| PP:", tf.reduce_sum(predicted_positives), "| Current Precision:", tf.cond(tf.greater(tf.reduce_sum(predicted_positives), 0), lambda: tf.reduce_sum(true_positives) / tf.reduce_sum(predicted_positives), lambda: 0.0)) # ... [后面的代码] ``` ### 2. 单元测试验证 创建单元测试验证边界情况: ```python import numpy as np import tensorflow as tf from tensorflow.keras import Input def test_class_specific_precision(): # 测试1: 没有预测样本的情况 metric = ClassSpecificPrecision(class_id=1) y_true = tf.constant([1, 1, 1]) y_pred = tf.constant([[0.9, 0.1], [0.8, 0.2], [0.7, 0.3]]) # 全部预测为类别0 metric.update_state(y_true, y_pred) precision = metric.result().numpy() assert precision == 0.0, f"Expected 0.0, got {precision}" # 测试2: 分母为零但分子不为零(理论上不可能) metric.reset_states() metric.true_positives.assign(5.0) metric.predicted_positives.assign(0.0) precision = metric.result().numpy() assert precision == 0.0, f"Expected 0.0, got {precision}" # 测试3: 完美精确率 metric.reset_states() y_true = tf.constant([1, 1, 1]) y_pred = tf.constant([[0.1, 0.9], [0.2, 0.8], [0.3, 0.7]]) # 全部预测为类别1 metric.update_state(y_true, y_pred) precision = metric.result().numpy() assert np.isclose(precision, 1.0), f"Expected 1.0, got {precision}" # 运行测试 test_class_specific_precision() ``` ## 使用注意事项 1. **输入数据格式**: - `y_true` 应为整数形式的类别标签(非one-hot- `y_pred` 应为模型输出的概率分布 2. **多GPU训练**: 在分布式训练环境中,确保使用`tf.distribute.Strategy`兼容的指标更新方式 3. **类别索引**: 确保`class_id`与数据中的实际类别索引一致(通常从0开始) ## 与引用内容的关系 1. 引用[1]展示了使用scikit-learn的分类报告,其中精确率都在合理范围内(0-1)。这验证了正确处理边界情况的重要性[^1]。 2. 引用[2]提到Keras回调函数,在自定义指标中,正确实现`reset_states()`方法对多epoch训练至关重要[^2]。 3. 引用[3]的多标签文本分类示例提醒我们,在单标签分类中精确率计算与多标签场景不同,需要区分处理[^3]。 通过上述修正方案,您的类别特定精确率指标应该能正确返回0-100%范围内的值。关键是要正确处理分母为零的边界情况,并确保状态变量的准确累积[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值