1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
將目標檢測模型的頭部改為 Transformer-style 架構是個很好的想法,這可以讓模型獲得更強的上下文建模能力和對象關係理解。以下是如何實現這一改進的研究和代碼框架:

### Transformer-Style 檢測頭的優勢

1. **全局上下文**:Transformer 可以通過自注意力機制捕獲圖像的全局上下文關係
2. **位置靈敏度**:使用位置編碼可以讓模型更好地理解對象的空間位置
3. **跨尺度特徵融合**:可以更有效地整合不同尺度的特徵
4. **對小物體敏感**:對於小目標的檢測往往有更好的性能

### DETR (DEtection TRansformer) 風格的檢測頭

以下是一個受 DETR 啟發的 Transformer-style 檢測頭的實現方案:

```python
import torch
import torch.nn as nn
import torch.nn.functional as F

class TransformerDetectionHead(nn.Module):
def __init__(self, in_channels, num_classes, num_queries=100, hidden_dim=256, nheads=8, num_encoder_layers=6, num_decoder_layers=6):
super().__init__()
self.num_queries = num_queries
self.num_classes = num_classes

# 將特徵圖轉換為序列
self.conv = nn.Conv2d(in_channels, hidden_dim, kernel_size=1)

# 位置編碼
self.pos_encoder = PositionEmbedding2D(hidden_dim)

# 查詢嵌入 (object queries)
self.query_embed = nn.Embedding(num_queries, hidden_dim)

# Transformer 編碼器-解碼器
encoder_layer = nn.TransformerEncoderLayer(d_model=hidden_dim, nhead=nheads)
self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_encoder_layers)

decoder_layer = nn.TransformerDecoderLayer(d_model=hidden_dim, nhead=nheads)
self.transformer_decoder = nn.TransformerDecoder(decoder_layer, num_layers=num_decoder_layers)

# 預測頭: 類別和邊界框
self.class_embed = nn.Linear(hidden_dim, num_classes + 1) # +1 for background
self.bbox_embed = MLP(hidden_dim, hidden_dim, 4, 3) # (cx, cy, w, h)

# 輸出激活
self.activation = nn.Sigmoid()

def forward(self, x):
# 特徵圖轉換
h = self.conv(x)

# 保存原始尺寸
bs, c, h, w = h.shape

# 添加位置編碼
pos = self.pos_encoder(h)

# 展平特徵圖為序列
h_flatten = h.flatten(2).permute(2, 0, 1) # (hw, bs, c)
pos_flatten = pos.flatten(2).permute(2, 0, 1) # (hw, bs, c)

# Transformer 編碼
memory = self.transformer_encoder(h_flatten + pos_flatten)

# 準備解碼器的查詢嵌入
query_embed = self.query_embed.weight.unsqueeze(1).repeat(1, bs, 1) # (num_queries, bs, hidden_dim)

# Transformer 解碼
tgt = torch.zeros_like(query_embed)
hs = self.transformer_decoder(tgt, memory) # (num_queries, bs, hidden_dim)

# 預測類別和邊界框
outputs_class = self.class_embed(hs) # (num_queries, bs, num_classes+1)
outputs_coord = self.activation(self.bbox_embed(hs)) # (num_queries, bs, 4), 值範圍 [0, 1]

# 重新排列輸出
outputs_class = outputs_class.permute(1, 0, 2) # (bs, num_queries, num_classes+1)
outputs_coord = outputs_coord.permute(1, 0, 2) # (bs, num_queries, 4)

return outputs_class, outputs_coord

# 輔助子模塊
class PositionEmbedding2D(nn.Module):
def __init__(self, hidden_dim=256):
super().__init__()
self.row_embed = nn.Embedding(50, hidden_dim // 2)
self.col_embed = nn.Embedding(50, hidden_dim // 2)

def forward(self, x):
h, w = x.shape[-2:]

# 限制最大尺寸
h = min(h, 50)
w = min(w, 50)

# 生成網格坐標
i = torch.arange(w, device=x.device)
j = torch.arange(h, device=x.device)

# 獲取位置嵌入
x_emb = self.col_embed(i)
y_emb = self.row_embed(j)

# 生成位置編碼網格
pos = torch.cat([
x_emb.unsqueeze(0).repeat(h, 1, 1),
y_emb.unsqueeze(1).repeat(1, w, 1),
], dim=-1).permute(2, 0, 1).unsqueeze(0).repeat(x.shape[0], 1, 1, 1)

return pos

class MLP(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim, num_layers):
super().__init__()
self.num_layers = num_layers
h = [hidden_dim] * (num_layers - 1)
self.layers = nn.ModuleList(nn.Linear(n, k) for n, k in zip([input_dim] + h, h + [output_dim]))

def forward(self, x):
for i, layer in enumerate(self.layers):
x = F.relu(layer(x)) if i < self.num_layers - 1 else layer(x)
return x
```

### 整合到現有模型中

這種 Transformer-style 檢測頭可以替換原來基於卷積的檢測頭,完整的模型架構會是:

```python
class TransformerObjectDetectionModel(nn.Module):
def __init__(self, unet, num_classes):
super().__init__()
self.encoder = UNetEncoder(unet)
self.detection_head = TransformerDetectionHead(
in_channels=1280, # UNet 特徵維度
num_classes=num_classes,
num_queries=100, # 每幅圖像查詢的目標數量
hidden_dim=256,
nheads=8
)

def forward(self, x, t=0):
# 通過encoder獲取特徵
feat = self.encoder(x, t)
# 通過Transformer檢測頭
return self.detection_head(feat)
```

### 改進損失函數

對於 Transformer-style 檢測頭,通常使用二分匹配損失(Hungarian Loss):

```python
class HungarianLoss(nn.Module):
def __init__(self, num_classes, weight_class=1, weight_bbox=5, weight_giou=2):
super().__init__()
self.num_classes = num_classes
self.weight_class = weight_class
self.weight_bbox = weight_bbox
self.weight_giou = weight_giou

# 類別損失
self.class_loss = nn.CrossEntropyLoss(reduction='none')

def forward(self, outputs, targets, device):
# 解包輸出
pred_logits, pred_boxes = outputs # (bs, num_queries, num_classes+1), (bs, num_queries, 4)

# 為每個批次個別處理
batch_size = pred_logits.shape[0]
total_loss = 0

for b in range(batch_size):
batch_targets = targets[b] # (n_targets, 6) - [class_idx, cx, cy, w, h, conf]

if batch_targets.shape[0] == 0:
# 如果沒有目標,只計算類別損失(所有都應該是背景)
background_pred = pred_logits[b, :, 0]
background_target = torch.ones_like(background_pred)
total_loss += self.class_loss(pred_logits[b], torch.zeros(pred_logits.shape[1], dtype=torch.long, device=device)).sum()
continue

# 目標類別
tgt_classes = batch_targets[:, 0].long()

# 目標邊界框 (cx, cy, w, h)
tgt_boxes = batch_targets[:, 1:5]

# 匈牙利匹配算法
indices = self.hungarian_matcher(pred_logits[b], pred_boxes[b], tgt_classes, tgt_boxes, device)
idx_pred, idx_tgt = indices

# 類別損失
class_loss = self.class_loss(pred_logits[b, idx_pred], tgt_classes[idx_tgt])

# 邊界框損失
# L1 損失
l1_loss = F.l1_loss(pred_boxes[b, idx_pred], tgt_boxes[idx_tgt], reduction='none').sum(-1)

# IoU/GIoU 損失
iou_loss = 1 - self.box_iou(
self.box_cxcywh_to_xyxy(pred_boxes[b, idx_pred]),
self.box_cxcywh_to_xyxy(tgt_boxes[idx_tgt])
)[0].diag()

# 總損失
loss = (
self.weight_class * class_loss +
self.weight_bbox * l1_loss +
self.weight_giou * iou_loss
).sum()

total_loss += loss

return total_loss / batch_size, {
'class_loss': self.weight_class * class_loss.mean().item() if 'class_loss' in locals() else 0.0,
'l1_loss': self.weight_bbox * l1_loss.mean().item() if 'l1_loss' in locals() else 0.0,
'iou_loss': self.weight_giou * iou_loss.mean().item() if 'iou_loss' in locals() else 0.0,
'total_loss': total_loss.item() / batch_size
}

# 匈牙利匹配器(略)
def hungarian_matcher(self, pred_logits, pred_boxes, tgt_classes, tgt_boxes, device):
# 簡易版本 - 實際應該使用Hungarian算法
n_preds = pred_logits.shape[0]
n_targets = tgt_classes.shape[0]

if n_targets == 0:
return (torch.arange(0, n_preds, device=device),
torch.arange(0, n_preds, device=device))

# 計算成本矩陣
cost_class = -pred_logits[:, tgt_classes]

# L1 距離
cost_bbox = torch.cdist(pred_boxes, tgt_boxes, p=1)

# IoU 距離
cost_giou = -self.generalized_box_iou(
self.box_cxcywh_to_xyxy(pred_boxes),
self.box_cxcywh_to_xyxy(tgt_boxes)
)

# 最終成本
cost = (
self.weight_class * cost_class +
self.weight_bbox * cost_bbox +
self.weight_giou * cost_giou
)

# 簡單貪婪匹配 (實際應使用匈牙利算法)
indices = []
for i in range(min(n_targets, n_preds)):
# 找到成本最小的匹配
if cost.numel() == 0:
break
i, j = torch.unravel_index(torch.argmin(cost), cost.shape)
indices.append((i.item(), j.item()))

# 移除已匹配的預測和目標
cost[i, :] = float('inf')
cost[:, j] = float('inf')

if not indices:
return torch.tensor([], device=device, dtype=torch.int64), torch.tensor([], device=device, dtype=torch.int64)

indices = list(zip(*indices))
return torch.tensor(indices[0], device=device, dtype=torch.int64), torch.tensor(indices[1], device=device, dtype=torch.int64)

# 邊界框工具函數
def box_cxcywh_to_xyxy(self, x):
x_c, y_c, w, h = x.unbind(-1)
b = [(x_c - 0.5 * w), (y_c - 0.5 * h),
(x_c + 0.5 * w), (y_c + 0.5 * h)]
return torch.stack(b, dim=-1)

def box_iou(self, boxes1, boxes2):
area1 = self.box_area(boxes1)
area2 = self.box_area(boxes2)

lt = torch.max(boxes1[:, None, :2], boxes2[:, :2]) # [N,M,2]
rb = torch.min(boxes1[:, None, 2:], boxes2[:, 2:]) # [N,M,2]

wh = (rb - lt).clamp(min=0) # [N,M,2]
inter = wh[:, :, 0] * wh[:, :, 1] # [N,M]

union = area1[:, None] + area2 - inter

iou = inter / union
return iou, inter

def box_area(self, boxes):
return (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])

def generalized_box_iou(self, boxes1, boxes2):
# 計算IoU
iou, inter = self.box_iou(boxes1, boxes2)

# 計算包含兩個框的最小外接矩形的面積
lt = torch.min(boxes1[:, None, :2], boxes2[:, :2])
rb = torch.max(boxes1[:, None, 2:], boxes2[:, 2:])

wh = (rb - lt).clamp(min=0) # [N,M,2]
area = wh[:, :, 0] * wh[:, :, 1]

return iou - (area - union) / area
```

### 訓練調整

使用Transformer架構的模型通常需要更多的訓練時間和更好的初始化:

1. **更長的訓練時間**:Transformer模型通常需要更多的訓練輪次
2. **更平緩的學習率**:建議使用較小的初始學習率,例如 1e-5
3. **權重初始化**:特別關注Transformer權重的初始化
4. **批次大小**:如果可能,使用較大的批次大小
5. **數據增強**:更多的數據增強有助於預防過擬合

### 結論與建議

轉換到Transformer-style的檢測頭是一個很好的想法,特別是如果您需要處理複雜場景和多物體檢測。然而,這也帶來了以下挑戰:

1. **計算成本**:Transformer模型的訓練和推理成本更高
2. **數據需求**:通常需要更多的訓練數據
3. **超參數敏感**:對學習率、批次大小等超參數更敏感

對於您的腰果檢測任務,如果數據集規模不大,建議先嘗試一個簡化版的Transformer架構,或者考慮使用預訓練的DETR或Mask R-CNN模型進行微調。

希望這些見解和代碼幫助能夠改進您的目標檢測性能!