loss.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. # Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import paddle
  15. import paddle.nn as nn
  16. import paddle.nn.functional as F
  17. from paddleseg.cvlibs import manager
  18. import cv2
  19. @manager.LOSSES.add_component
  20. class MRSD(nn.Layer):
  21. def __init__(self, eps=1e-6):
  22. super().__init__()
  23. self.eps = eps
  24. def forward(self, logit, label, mask=None):
  25. """
  26. Forward computation.
  27. Args:
  28. logit (Tensor): Logit tensor, the data type is float32, float64.
  29. label (Tensor): Label tensor, the data type is float32, float64. The shape should equal to logit.
  30. mask (Tensor, optional): The mask where the loss valid. Default: None.
  31. """
  32. if len(label.shape) == 3:
  33. label = label.unsqueeze(1)
  34. sd = paddle.square(logit - label)
  35. loss = paddle.sqrt(sd + self.eps)
  36. if mask is not None:
  37. mask = mask.astype('float32')
  38. if len(mask.shape) == 3:
  39. mask = mask.unsqueeze(1)
  40. loss = loss * mask
  41. loss = loss.sum() / (mask.sum() + self.eps)
  42. mask.stop_gradient = True
  43. else:
  44. loss = loss.mean()
  45. return loss
  46. @manager.LOSSES.add_component
  47. class GradientLoss(nn.Layer):
  48. def __init__(self, eps=1e-6):
  49. super().__init__()
  50. self.kernel_x, self.kernel_y = self.sobel_kernel()
  51. self.eps = eps
  52. def forward(self, logit, label, mask=None):
  53. if len(label.shape) == 3:
  54. label = label.unsqueeze(1)
  55. if mask is not None:
  56. if len(mask.shape) == 3:
  57. mask = mask.unsqueeze(1)
  58. logit = logit * mask
  59. label = label * mask
  60. loss = paddle.sum(
  61. F.l1_loss(self.sobel(logit), self.sobel(label), 'none')) / (
  62. mask.sum() + self.eps)
  63. else:
  64. loss = F.l1_loss(self.sobel(logit), self.sobel(label), 'mean')
  65. return loss
  66. def sobel(self, input):
  67. """Using Sobel to compute gradient. Return the magnitude."""
  68. if not len(input.shape) == 4:
  69. raise ValueError("Invalid input shape, we expect NCHW, but it is ",
  70. input.shape)
  71. n, c, h, w = input.shape
  72. input_pad = paddle.reshape(input, (n * c, 1, h, w))
  73. input_pad = F.pad(input_pad, pad=[1, 1, 1, 1], mode='replicate')
  74. grad_x = F.conv2d(input_pad, self.kernel_x, padding=0)
  75. grad_y = F.conv2d(input_pad, self.kernel_y, padding=0)
  76. mag = paddle.sqrt(grad_x * grad_x + grad_y * grad_y + self.eps)
  77. mag = paddle.reshape(mag, (n, c, h, w))
  78. return mag
  79. def sobel_kernel(self):
  80. kernel_x = paddle.to_tensor([[-1.0, 0.0, 1.0], [-2.0, 0.0, 2.0],
  81. [-1.0, 0.0, 1.0]]).astype('float32')
  82. kernel_x = kernel_x / kernel_x.abs().sum()
  83. kernel_y = kernel_x.transpose([1, 0])
  84. kernel_x = kernel_x.unsqueeze(0).unsqueeze(0)
  85. kernel_y = kernel_y.unsqueeze(0).unsqueeze(0)
  86. kernel_x.stop_gradient = True
  87. kernel_y.stop_gradient = True
  88. return kernel_x, kernel_y
  89. @manager.LOSSES.add_component
  90. class LaplacianLoss(nn.Layer):
  91. """
  92. Laplacian loss is refer to
  93. https://github.com/JizhiziLi/AIM/blob/master/core/evaluate.py#L83
  94. """
  95. def __init__(self):
  96. super().__init__()
  97. self.gauss_kernel = self.build_gauss_kernel(
  98. size=5, sigma=1.0, n_channels=1)
  99. def forward(self, logit, label, mask=None):
  100. if len(label.shape) == 3:
  101. label = label.unsqueeze(1)
  102. if mask is not None:
  103. if len(mask.shape) == 3:
  104. mask = mask.unsqueeze(1)
  105. logit = logit * mask
  106. label = label * mask
  107. pyr_label = self.laplacian_pyramid(label, self.gauss_kernel, 5)
  108. pyr_logit = self.laplacian_pyramid(logit, self.gauss_kernel, 5)
  109. loss = sum(F.l1_loss(a, b) for a, b in zip(pyr_label, pyr_logit))
  110. return loss
  111. def build_gauss_kernel(self, size=5, sigma=1.0, n_channels=1):
  112. if size % 2 != 1:
  113. raise ValueError("kernel size must be uneven")
  114. grid = np.float32(np.mgrid[0:size, 0:size].T)
  115. gaussian = lambda x: np.exp((x - size // 2)**2 / (-2 * sigma**2))**2
  116. kernel = np.sum(gaussian(grid), axis=2)
  117. kernel /= np.sum(kernel)
  118. kernel = np.tile(kernel, (n_channels, 1, 1))
  119. kernel = paddle.to_tensor(kernel[:, None, :, :])
  120. kernel.stop_gradient = True
  121. return kernel
  122. def conv_gauss(self, input, kernel):
  123. n_channels, _, kh, kw = kernel.shape
  124. x = F.pad(input, (kh // 2, kw // 2, kh // 2, kh // 2), mode='replicate')
  125. x = F.conv2d(x, kernel, groups=n_channels)
  126. return x
  127. def laplacian_pyramid(self, input, kernel, max_levels=5):
  128. current = input
  129. pyr = []
  130. for level in range(max_levels):
  131. filtered = self.conv_gauss(current, kernel)
  132. diff = current - filtered
  133. pyr.append(diff)
  134. current = F.avg_pool2d(filtered, 2)
  135. pyr.append(current)
  136. return pyr