Pytorch: [PyTorch][Permintaan Fitur] Perataan Label untuk CrossEntropyLoss

Dibuat pada 10 Mei 2018  ·  22Komentar  ·  Sumber: pytorch/pytorch

Halo kawan-kawan. Jenis torch.LongTensor dari target akan menghambat implementasi seperti beberapa metode dalam referensi . Jadi apakah ada kemungkinan untuk menambahkan Arg: label_smoothing untuk torch.nn.CrossEntropyLoss() , atau mungkin cukup menambahkan dokumen untuk menunjukkan cara mengonversi target menjadi one-hot vector agar berfungsi torch.nn.CrossEntropyLoss() bersama-sama, atau cara sederhana lainnya? Terima kasih.

cc @ezyang @gchanan @zou3519 @bdhirsh @albanD @mruberry

enhancement high priority loss nn triage review triaged

Komentar yang paling membantu

Ini alat saya

class LabelSmoothingLoss(nn.Module):
    def __init__(self, classes, smoothing=0.0, dim=-1):
        super(LabelSmoothingLoss, self).__init__()
        self.confidence = 1.0 - smoothing
        self.smoothing = smoothing
        self.cls = classes
        self.dim = dim

    def forward(self, pred, target):
        pred = pred.log_softmax(dim=self.dim)
        with torch.no_grad():
            # true_dist = pred.data.clone()
            true_dist = torch.zeros_like(pred)
            true_dist.fill_(self.smoothing / (self.cls - 1))
            true_dist.scatter_(1, target.data.unsqueeze(1), self.confidence)
        return torch.mean(torch.sum(-true_dist * pred, dim=self.dim))

Semua 22 komentar

Lihat https://discuss.pytorch.org/t/cross-entropy-with-one-hot-targets/13580/5. Fungsi cross_entropy() yang ditampilkan di sana harus bekerja dengan label yang dihaluskan yang memiliki dimensi yang sama dengan keluaran jaringan.

Saya tidak berpikir CrossEntropyLoss() harus secara langsung mendukung opsi label_smoothing , karena perataan label dapat dilakukan dengan berbagai cara dan pemulusan itu sendiri dapat dengan mudah dilakukan secara manual oleh pengguna. Tapi saya setuju setidaknya harus disebutkan dalam dokumen bagaimana menangani target yang tidak dapat diwakili oleh nilai skalar, atau menambahkan dukungan untuk meneruskan target (k-hot/smoothed) ke CrossEntropyLoss .

Mungkin kita perlu sth seperti NonSparseCrossEntropy ? (yah.. sulit untuk menyebutkannya)

Ini alat saya

class LabelSmoothingLoss(nn.Module):
    def __init__(self, classes, smoothing=0.0, dim=-1):
        super(LabelSmoothingLoss, self).__init__()
        self.confidence = 1.0 - smoothing
        self.smoothing = smoothing
        self.cls = classes
        self.dim = dim

    def forward(self, pred, target):
        pred = pred.log_softmax(dim=self.dim)
        with torch.no_grad():
            # true_dist = pred.data.clone()
            true_dist = torch.zeros_like(pred)
            true_dist.fill_(self.smoothing / (self.cls - 1))
            true_dist.scatter_(1, target.data.unsqueeze(1), self.confidence)
        return torch.mean(torch.sum(-true_dist * pred, dim=self.dim))

Saya setuju dengan @mdraw
Pilihan yang baik adalah melakukannya dalam dua langkah:

  1. Gunakan fungsi untuk mendapatkan label halus
def smooth_one_hot(true_labels: torch.Tensor, classes: int, smoothing=0.0):
    """
    if smoothing == 0, it's one-hot method
    if 0 < smoothing < 1, it's smooth method

    """
    assert 0 <= smoothing < 1
    confidence = 1.0 - smoothing
    label_shape = torch.Size((true_labels.size(0), classes))
    with torch.no_grad():
        true_dist = torch.empty(size=label_shape, device=true_labels.device)
        true_dist.fill_(smoothing / (classes - 1))
        true_dist.scatter_(1, true_labels.data.unsqueeze(1), confidence)
    return true_dist
  1. Buat target CrossEntropyLoss mendukung k-hot/smoothed.

Kemudian kita bisa menggunakannya seperti

Loss = CrossEntropyLoss(NonSparse=True, ...)
. . .
data = ...
labels = ...

outputs = model(data)

smooth_label = smooth_one_hot(labels, ...)
loss = (outputs, smooth_label)
...

Omong-omong, saya menguji alat saya di ImageNet, kelihatannya bagus

|model | zaman| dtype |ukuran batch*|gpus | lr | trik|top1/top5 |perbaiki |
|:----:|:-----:|:------:|:---------:|:----:|:---:|: ------:|:---------:|:------:|
|resnet50|120 |FP16 |128 | 8 |0,4 | - |77.35/- |dasar|
|resnet50|120 |FP16 |128 | 8 |0,4 |Perataan label|77,78/93,80| +0.43 |

Saya percaya @zhangguanheng66 mengatakan bahwa ini adalah sesuatu yang mungkin bisa dia lihat di masa depan.

Cukup gunakan torch.nn.KLDivLoss. Sama.


Pembaruan: tidak sama.

Saya percaya ini mirip dengan apa yang diterapkan lib Snorkel baru:
https://snorkel.readthedocs.io/en/master/packages/_autosummary/classification/snorkel.classification.cross_entropy_with_probs.html

Hanya beberapa info tambahan tentang bagaimana orang-orang mengatasi masalah ini

lihat https://github.com/NVIDIA/DeepLearningExamples/tree/master/PyTorch/Classification/RN50v1.5 untuk mengetahui bagaimana Nvidia melakukannya yang mungkin membantu?

@suanrong Terima kasih banyak.

====
Dan mungkin ini bermanfaat bagi orang lain yang membaca masalah ini

Perhatikan bahwa entropi silang untuk label non 0/1 tidak simetris, yang bisa menjadi penjelasan untuk kinerja yang buruk.
https://discuss.pytorch.org/t/cross-entropy-for-soft-label/16093/2

Implementasi yang disarankan:

class LabelSmoothLoss(nn.Module):

    def __init__(self, smoothing=0.0):
        super(LabelSmoothLoss, self).__init__()
        self.smoothing = smoothing

    def forward(self, input, target):
        log_prob = F.log_softmax(input, dim=-1)
        weight = input.new_ones(input.size()) * \
            self.smoothing / (input.size(-1) - 1.)
        weight.scatter_(-1, target.unsqueeze(-1), (1. - self.smoothing))
        loss = (-weight * log_prob).sum(dim=-1).mean()
        return loss

Saya telah memeriksa bahwa:
(1) Saat menghaluskan=0.0, outputnya sama dengan nn.CrossEntropyLoss dalam presisi 1e-5 .
(2) Saat menghaluskan>0,0, jumlah bobot pada kelas yang berbeda weight.sum(dim=-1) selalu 1.

Implementasi di sini kekurangan fitur bobot kelas.
((

Cukup gunakan torch.nn.KLDivLoss. Sama.

bisa tolong lebih di perjelas

Cukup gunakan torch.nn.KLDivLoss. Sama.

bisa tolong lebih di perjelas

Asumsikan Anda sudah memiliki label yang dihaluskan, Anda bisa menggunakan torch.nn.KLDivLoss karena perbedaan di antara keduanya adalah entropi label dan konstanta.

@PistonY mengapa tidak menggunakan cara ini lebih sederhana:

with torch.no_grad():
    confidence = 1.0 - smoothing_factor
    true_dist = torch.mul(labels, confidence)
    true_dist = torch.add(true_dist, smoothing_factor / (classNum - 1))
    print(true_dist)
return true_dist

Implementasi di sini kekurangan fitur bobot kelas.

Bisakah saya mengalikan bobot kelas pada tensor label yang dihaluskan?

def smooth_one_hot(true_labels: torch.Tensor, class: int, smoothing=0.0):
"""
jika menghaluskan == 0, itu metode satu-panas
jika 0 < smoothing < 1, itu metode smooth

"""
assert 0 <= smoothing < 1
confidence = 1.0 - smoothing
label_shape = torch.Size((true_labels.size(0), classes))
with torch.no_grad():
    true_dist = torch.empty(size=label_shape, device=true_labels.device)
    true_dist.fill_(smoothing / (classes - 1))
    true_dist.scatter_(1, true_labels.data.unsqueeze(1), confidence)
return true_dist

```

Masalah dengan implementasi ini adalah sangat sensitif terhadap jumlah kelas

Di mana n_classes adalah 2, perataan apa pun di atas 0,5 akan membalikkan label, yang saya yakin tidak diinginkan orang tersebut; ketika n_classes adalah 3 itu smoothing di atas 2/3, dan 0,75 untuk 4 kelas. Jadi mungkin:

assert 0 <= smoothing < (classes-1)/classes akan menangkap masalah ini, tetapi saya merasa perataan perlu mempertimbangkan jumlah kelas?

def smooth_one_hot(true_labels: torch.Tensor, class: int, smoothing=0.0):

"""
if smoothing == 0, it's one-hot method
if 0 < smoothing < 1, it's smooth method

"""
assert 0 <= smoothing < 1
confidence = 1.0 - smoothing
label_shape = torch.Size((true_labels.size(0), classes))
with torch.no_grad():
    true_dist = torch.empty(size=label_shape, device=true_labels.device)
    true_dist.fill_(smoothing / (classes - 1))
    true_dist.scatter_(1, true_labels.data.unsqueeze(1), confidence)
return true_dist

```

Masalah dengan implementasi ini adalah sangat sensitif terhadap jumlah kelas

Di mana n_classes adalah 2, perataan apa pun di atas 0,5 akan membalikkan label, yang saya yakin tidak diinginkan orang tersebut; ketika n_classes adalah 3 itu smoothing di atas 2/3, dan 0,75 untuk 4 kelas. Jadi mungkin:

assert 0 <= smoothing < (classes-1)/classes akan menangkap masalah ini, tetapi saya merasa perataan perlu mempertimbangkan jumlah kelas?

Itu ide yang bijak menurut saya.

Terima kasih atas diskusinya. Ada beberapa poin yang masih belum jelas dan terlihat seperti kesalahan bagi saya:

  • tensor bobot dalam implementasi @PistonY
  • kesetaraan antara KL divergence dan label-smoothing ( @suanrong )

Tentang bobot:

Kertas penghalus label menyatakan y_k = smoothing / n_classes + (1 - smoothing) * y_{one hot} . Jadi nilai bobotnya adalah smoothing / n_classes untuk indeks selain target, dan smoothing / n_classes + (1 - smoothing) untuk kelas target. Namun dalam implementasi @PistonY , fungsi torch.scatter_ menimpa nilai target menjadi (1 - smoothing) (dan istilah konstan menghilang).
Selain itu, saya tidak begitu mengerti mengapa kita menggunakan n_classes -= 1 dalam perhitungan (?)

Tentang kesetaraan antara divergensi KL dan perataan label:

Kehilangan lintas-entropi perataan label berbunyi, dengan y bobot yang disebutkan di atas,

LS(x, y) = - sum_k {y[k] * log-prob(x)}
         = - sum_k {y[k] * log(exp(x[k]) / (sum_j exp(x[j])))}
         = - sum_k {y[k] * (x[k] - log-sum-exp(x))}
         = - sum_k {y[k] * x[k]} + log-sum-exp(x)

di mana baris ketiga hingga keempat menggunakan fakta bahwa sum_k y[k] = smoothing / n_classes * n_classes + (1 - smoothing) = 1 .

Kerugian divergensi KL berbunyi,

KL(x, y) = - sum_k {y[k] * x[k] - y[k] * log(y[k])
         = - sum_k {y[k] * x[k]} - sum_k {y[k] * log(y[k])}
         = - sum_k {y[k] * x[k]} - Const.

Jadi pada akhirnya kita memiliki LS(x, y) = KL(x, y) + log-sum-exp(x) + Const. , di mana Const. adalah suku konstan yang sesuai dengan entropi y , yang memang konstan dalam pengaturan multikelas. Tapi bagaimana dengan istilah log-sum-exp ?

Saya melakukan beberapa perhitungan menggunakan fungsi lintas entropi khusus yang menerima target lunak , dan itu menunjukkan bahwa itu memang sama dengan kerugian KLDiv ditambah log-sum-exp , hingga istilah konstan yang sesuai dengan entropi y . Apakah ada asumsi pada log yang membuatnya masuk akal untuk menjatuhkan istilah ini?

Terima kasih banyak atas klarifikasinya.
Bersulang !

Terima kasih @antrec !

Kamu benar. Saya mengabaikan fungsi logsoftmax dan membuat kesalahan.

Implementasi fungsi kehilangan lintas entropi perataan label:

import torch.nn.functional as F
def linear_combination(x, y, epsilon): 
    return epsilon*x + (1-epsilon)*y

def reduce_loss(loss, reduction='mean'):
    return loss.mean() if reduction=='mean' else loss.sum() if reduction=='sum' else loss

class LabelSmoothingCrossEntropy(nn.Module):
    def __init__(self, epsilon:float=0.1, reduction='mean'):
        super().__init__()
        self.epsilon = epsilon
        self.reduction = reduction

    def forward(self, preds, target):
        n = preds.size()[-1]
        log_preds = F.log_softmax(preds, dim=-1)
        loss = reduce_loss(-log_preds.sum(dim=-1), self.reduction)
        nll = F.nll_loss(log_preds, target, reduction=self.reduction)
        return linear_combination(loss/n, nll, self.epsilon)

Menabrak hi-pri berdasarkan aktivitas

Apakah halaman ini membantu?
0 / 5 - 0 peringkat