F.binary_cross_entropy与sklearn.metric.log_loss的比较

F.binary_cross_entropysklearn.metrics.log_loss 都可以用来计算二分类或多分类任务中的交叉熵损失,但它们的使用场景、实现方式和功能有所不同。以下是两者的主要区别:


1. 所属库

  • F.binary_cross_entropy

    • 来自 PyTorch 的 torch.nn.functional 模块。
    • 主要用于深度学习模型训练时计算损失值,并支持自动求导(autograd)以便反向传播优化模型参数。
  • log_loss

    • 来自 Scikit-learn 的 sklearn.metrics 模块。
    • 主要用于评估模型性能,通常在模型训练完成后使用,不支持自动求导。

2. 输入格式

  • F.binary_cross_entropy

    • 输入是张量(tensor),适用于 PyTorch 模型。
    • 要求输入的预测值为概率值(通常是通过激活函数如 Sigmoid 输出的值,范围 [0, 1])。
    • 标签也必须是张量,形状与预测值一致。
    import torch
    import torch.nn.functional as F
    
    preds = torch.tensor([0.9, 0.1, 0.8])  # 模型预测的概率
    targets = torch.tensor([1, 0, 1])      # 真实标签
    loss = F.binary_cross_entropy(preds, targets)
    
  • log_loss

    • 输入是 NumPy 数组或 Python 列表。
    • 预测值也可以是概率值(范围 [0, 1]),但标签通常是以整数形式表示的类别(例如 0 或 1)。
    from sklearn.metrics import log_loss
    
    preds = [[0.9], [0.1], [0.8]]  # 模型预测的概率
    targets = [1, 0, 1]            # 真实标签
    loss = log_loss(targets, preds)
    

3. 计算方式

  • F.binary_cross_entropy

    • 直接计算二分类交叉熵损失。
    • 公式如下:
      Loss=−1N∑i=1N[yi⋅log⁡(pi)+(1−yi)⋅log⁡(1−pi)]\text{Loss} = -\frac{1}{N} \sum_{i=1}^{N} \left[ y_i \cdot \log(p_i) + (1 - y_i) \cdot \log(1 - p_i) \right]Loss=N1i=1N[yilog(pi)+(1yi)log(1pi)]
      • yiy_iyi 是真实标签(0 或 1)。
      • pip_ipi 是预测概率(范围 [0, 1])。
    • 支持逐元素计算,返回的是一个标量(平均损失)。
  • log_loss

    • 默认计算多分类交叉熵损失,但可以通过设置 labels 参数处理二分类问题。
    • 对于二分类问题,公式相同,但输入格式可能略有不同(如需要二维数组)。
    • 自动对多个样本取平均。

4. 功能与用途

  • F.binary_cross_entropy

    • 用于模型训练期间计算损失值。
    • 支持自动求导,便于反向传播更新模型参数。
    • 可用于动态调整模型。
  • log_loss

    • 用于模型评估阶段,衡量模型预测的质量。
    • 无法直接用于模型训练,因为没有自动求导功能。

5. 是否支持加权

  • F.binary_cross_entropy

    • 支持通过 weight 参数为每个样本或类别设置权重。
      loss = F.binary_cross_entropy(preds, targets, weight=torch.tensor([0.5, 1.0]))
      
  • log_loss

    • 不支持样本权重,但可以通过预处理数据来模拟加权效果。

6. 多分类支持

  • F.binary_cross_entropy

    • 仅支持二分类问题。
    • 如果需要处理多分类问题,可以使用 F.cross_entropy
  • log_loss

    • 原生支持多分类问题,只需提供多维概率分布即可。

7. 性能与效率

  • F.binary_cross_entropy

    • 使用 GPU 加速时性能更高,适合大规模深度学习任务。
    • 需要将数据转换为张量格式。
  • log_loss

    • 通常运行在 CPU 上,适合小规模数据集或模型评估。
    • 更方便直接使用 NumPy 数据。

总结对比表

特性F.binary_cross_entropylog_loss
所属库PyTorchScikit-learn
适用场景模型训练模型评估
输入格式张量NumPy 数组或列表
是否支持自动求导
是否支持加权
多分类支持不支持(需用 F.cross_entropy支持
性能高效(支持 GPU)一般(CPU 为主)

选择建议

  • 如果正在使用 PyTorch 进行深度学习模型训练,推荐使用 F.binary_cross_entropy
  • 如果已经完成模型训练并希望评估模型性能,推荐使用 log_loss
  • 如果需要处理多分类问题,可以使用 F.cross_entropy(PyTorch)或 log_loss(Scikit-learn)。
# 这是一个示例 Python 脚本。 # 按 Shift+F10 执行或将其替换为您的代码。 # 按 双击 Shift 在所有地方搜索类、文件、工具窗口、操作和设置。 import argparse import math import pickle import torch import torch.nn as nn import torch.nn.functional as F from tqdm import tqdm from omegaconf import OmegaConf from sklearn.metrics import f1_score from torch.utils.data import Dataset, DataLoader from torch.nn import TransformerEncoderLayer, TransformerEncoder restypes = [ 'A', 'R', 'N', 'D', 'C', 'Q', 'E', 'G', 'H', 'I', 'L', 'K', 'M', 'F', 'P', 'S', 'T', 'W', 'Y', 'V' ] unsure_restype = 'X' unknown_restype = 'U' def make_dataset(data_config, train_rate=0.7, valid_rate=0.2): data_path = data_config.data_path with open(data_path, 'rb') as f: data = pickle.load(f) total_number = len(data) train_sep = int(total_number * train_rate) valid_sep = int(total_number * (train_rate + valid_rate)) train_data_dicts = data[:train_sep] valid_data_dicts = data[train_sep:valid_sep] test_data_dicts = data[valid_sep:] train_dataset = DisProtDataset(train_data_dicts) valid_dataset = DisProtDataset(valid_data_dicts) test_dataset = DisProtDataset(test_data_dicts) return train_dataset, valid_dataset, test_dataset class DisProtDataset(Dataset): def __init__(self, dict_data): sequences = [d['sequence'] for d in dict_data] labels = [d['label'] for d in dict_data] assert len(sequences) == len(labels) self.sequences = sequences self.labels = labels self.residue_mapping = {'X':20} self.residue_mapping.update(dict(zip(restypes, range(len(restypes))))) def __len__(self): return len(self.sequences) def __getitem__(self, idx): sequence = torch.zeros(len(self.sequences[idx]), len(self.residue_mapping)) for i, c in enumerate(self.sequences[idx]): if c not in restypes: c = 'X' sequence[i][self.residue_mapping[c]] = 1 label = torch.tensor([int(c) for c in self.labels[idx]], dtype=torch.long) return sequence, label class PositionalEncoding(nn.Module): def __init__(self, d_model, dropout=0.0, max_len=40): super().__init__() position = torch.arange(max_len).unsqueeze(1) div_term = torch.exp( torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model) ) pe = torch.zeros(1, max_len, d_model) pe[0, :, 0::2] = torch.sin(position * div_term) pe[0, :, 1::2] = torch.cos(position * div_term) self.register_buffer("pe", pe) self.dropout = nn.Dropout(p=dropout) def forward(self, x): if len(x.shape) == 3: x = x + self.pe[:, : x.size(1)] elif len(x.shape) == 4: x = x + self.pe[:, :x.size(1), None, :] return self.dropout(x) class DisProtModel(nn.Module): def __init__(self, model_config): super().__init__() self.d_model = model_config.d_model self.n_head = model_config.n_head self.n_layer = model_config.n_layer self.input_layer = nn.Linear(model_config.i_dim, self.d_model) self.position_embed = PositionalEncoding(self.d_model, max_len=20000) self.input_norm = nn.LayerNorm(self.d_model) self.dropout_in = nn.Dropout(p=0.1) encoder_layer = TransformerEncoderLayer( d_model=self.d_model, nhead=self.n_head, activation='gelu', batch_first=True) self.transformer = TransformerEncoder(encoder_layer, num_layers=self.n_layer) self.output_layer = nn.Sequential( nn.Linear(self.d_model, self.d_model), nn.GELU(), nn.Dropout(p=0.1), nn.Linear(self.d_model, model_config.o_dim) ) def forward(self, x): x = self.input_layer(x) x = self.position_embed(x) x = self.input_norm(x) x = self.dropout_in(x) x = self.transformer(x) x = self.output_layer(x) return x def metric_fn(pred, gt): pred = pred.detach().cpu() gt = gt.detach().cpu() pred_labels = torch.argmax(pred, dim=-1).view(-1) gt_labels = gt.view(-1) score = f1_score(y_true=gt_labels, y_pred=pred_labels, average='micro') return score if __name__ == '__main__': device = 'cuda' if torch.cuda.is_available() else 'cpu' parser = argparse.ArgumentParser('IDRs prediction') parser.add_argument('--config_path', default='./config.yaml') args = parser.parse_args() config = OmegaConf.load(args.config_path) train_dataset, valid_dataset, test_dataset = make_dataset(config.data) train_dataloader = DataLoader(dataset=train_dataset, **config.train.dataloader) valid_dataloader = DataLoader(dataset=valid_dataset, batch_size=1, shuffle=False) model = DisProtModel(config.model) model = model.to(device) optimizer = torch.optim.AdamW(model.parameters(), lr=config.train.optimizer.lr, weight_decay=config.train.optimizer.weight_decay) loss_fn = nn.CrossEntropyLoss() model.eval() metric = 0. with torch.no_grad(): for sequence, label in valid_dataloader: sequence = sequence.to(device) label = label.to(device) pred = model(sequence) metric += metric_fn(pred, label) print("init f1_score:", metric / len(valid_dataloader)) for epoch in range(config.train.epochs): # train loop progress_bar = tqdm( train_dataloader, initial=0, desc=f"epoch:{epoch:03d}", ) model.train() total_loss = 0. for sequence, label in progress_bar: sequence = sequence.to(device) label = label.to(device) pred = model(sequence) loss = loss_fn(pred.permute(0, 2, 1), label) progress_bar.set_postfix(loss=loss.item()) total_loss += loss.item() optimizer.zero_grad() loss.backward() optimizer.step() avg_loss = total_loss / len(train_dataloader) # valid loop model.eval() metric = 0. with torch.no_grad(): for sequence, label in valid_dataloader: sequence = sequence.to(device) label = label.to(device) pred = model(sequence) metric += metric_fn(pred, label) print(f"avg_training_loss: {avg_loss}, f1_score: {metric / len(valid_dataloader)}") # 保存当前 epoch 的模型 save_path = f"model.pkl" torch.save(model.state_dict(), save_path) print(f"Model saved to {save_path}") 帮我分析一下这个代码是干什么的
最新发布
07-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值