🌈?個人主頁:十二月的貓-CSDN博客
🔥?系列專欄:?🏀大模型實戰訓練營_十二月的貓的博客-CSDN博客💪🏻?十二月的寒冬阻擋不了春天的腳步,十二點的黑夜遮蔽不住黎明的曙光
目錄
1. 前言
2. LayoutPrompt介紹
3. 序列化復習?
4. LayoutPrompt · 動態選擇模塊
4.1 動態選擇模塊的父類
4.2?基于元素類型相似度的動態選擇
4.3 基于類型+尺寸相似度的的動態選擇
5. LayoutPrompt ·排名模塊
6. 總結
1. 前言
? ? ? ? 貓貓不知道大家有沒有思考過圖片布局生成模型,這算是生成模型的一個非常小的子任務了。前面帶大家學習過生成模型,包括GAN、Diffusion等。這些都算是生成模型的研究子領域,利用這些子領域的知識,我們可以來研究具體的任務,例如生成圖片、按照語言提示生成圖片、按照布局提示生成圖片等。
????????同樣生成布局也是生成模型中的一個具體任務,其實思路也是非常簡單。生成圖片這個任務中更具體的任務是生成海報、生成照片、生成動漫圖片等。因為直接生成一個海報難度太大,我們就先去生成布局,然后在具體布局的約束下去具體生成完整圖片。可以簡單給大家看一下布局是什么:
? ? ? ? 貓貓研究這個呢,主要還是因為創新實訓和軟件創新大賽兩個需要。同時由于貓貓也研究過一點生成模型,因此這個學期就研究一下這個領域啦~~如果大家也對這個領域感興趣,可以關注我們團隊的專欄(布局生成模型是我們海報生成系統中的一個子模塊,希望更多貓友參與到我們的開發當中哦):大模型實戰訓練營_十二月的貓的博客-CSDN博客
2. LayoutPrompt介紹
總的來說,為了完成LayoutPrompt模型,我們需要完成的子模塊有:
- 數據預處理模塊:該模塊主要就是利用基礎數據預處理方法對數據集中的所有數據樣本進行預處理。
- 動態樣本選擇模塊:從訓練集中檢索最相關的樣本,然后作為最直接的上下文(約束信息)送給大語言模型。
- 布局序列化模塊:用于將上面所選的樣本布局轉化為序列表述(因為大語言模型對序列化輸入有更好的效果)。序列化數據就是類似 自然語言、代碼等。
- 大語言模型模塊:將序列化處理后的所有樣本一起送給大語言模型,讓大語言模型參考的情況下給出自己的答案。
- 大語言模型解析模塊:用于將大語言模型給出的布局結果解析為標準化的輸出。
- 布局排序模塊(布局評價模塊):評價大語言模型生成的布局的質量分數,并做一個排序。?
具體的模型圖如下:
模型運行具體流程如下:
- 用戶輸入前導信息,例如畫布大小,任務類型等,數據預處理模塊預處理數據庫(用戶根據自己的任務可以選擇數據庫,如海報布局數據庫、手機UI設計數據庫)中的所有數據。
- 動態樣本選擇模塊得到處理后的數據庫數據,然后根據用戶輸入的前導信息選擇合適的example樣本。
- 將樣本+前導信息+測試樣本送給大語言模型。
- 由大語言模型生成最終的layout,送給Rank模塊。
- Rank模塊排序后分數最高的就是最終輸出。
Layout生成模式選擇(任務類型):
- 元素類型限制
- 元素類型+尺寸限制
- 元素相對位置限制
- 布局補全
- 布局修正+補全
- 內容避讓
- 文本描述轉布局結構
上面這些都是我們layout生成可以選擇的模式,這些模式適應不同的任務背景,可以供大家選擇。
3. 序列化復習?
? ? ? ? 簡單復習一下序列化處理的部分,序列化處理的本質:一、固定輸入形式;二、為不同任務設定不同的Prompt。更加局限狹義的說,序列化模塊最關鍵的代碼如下(構建Prompt):
prompt分為:前導任務信息Prompt、提供example信息Prompt、
def build_prompt(serializer,exemplars,test_data,dataset,max_length=8000,separator_in_samples="\n",separator_between_samples="\n\n",
):# 前置任務信息prompt = [PREAMBLE.format(serializer.task_type, LAYOUT_DOMAIN[dataset], *CANVAS_SIZE[dataset])]# 具體prompt限制信息:layout類型、尺寸等(論文例子是用seq限制,更好理解,但是也有seq限制的)# 輸入輸入限制的是prompt用seq形式還是用html形式。輸入限制的是constraint_type后面的東西,輸出限制的是example的prompt輸入形式。這兩個都是完整Prompt的一部分for i in range(len(exemplars)):_prompt = (serializer.build_input(exemplars[i])+ separator_in_samples+ serializer.build_output(exemplars[i]))# 前導信息+限制信息if len(separator_between_samples.join(prompt) + _prompt) <= max_length:prompt.append(_prompt)else:breakprompt.append(serializer.build_input(test_data) + separator_in_samples)return separator_between_samples.join(prompt)
- 前置任務信息Prompt部分:任務類型+layout應用區域+畫布尺寸。如下
PREAMBLE = ("Please generate a layout based on the given information. ""You need to ensure that the generated layout looks realistic, with elements well aligned and avoiding unnecessary overlap.\n""Task Description: {}\n""Layout Domain: {} layout\n""Canvas Size: canvas width is {}px, canvas height is {}px" )
- 任務類型例如:
1. generation conditioned on given element types
2. generation conditioned on given element types and sizes
3. "generation conditioned on given element relationships\n""'A left B' means that the center coordinate of A is to the left of the center coordinate of B. ""'A right B' means that the center coordinate of A is to the right of the center coordinate of B. ""'A top B' means that the center coordinate of A is above the center coordinate of B. ""'A bottom B' means that the center coordinate of A is below the center coordinate of B. ""'A center B' means that the center coordinate of A and the center coordinate of B are very close. ""'A smaller B' means that the area of A is smaller than the ares of B. ""'A larger B' means that the area of A is larger than the ares of B. ""'A equal B' means that the area of A and the ares of B are very close. ""Here, center coordinate = (left + width / 2, top + height / 2), ""area = width * height"
- layout應用區域如下:
LAYOUT_DOMAIN = {"rico": "android","publaynet": "thesis poster", # 適應任務做的修改"posterlayout": "poster","webui": "web",
}
- 畫布尺寸:
CANVAS_SIZE = {"rico": (90, 160),"publaynet": (120, 160),"posterlayout": (102, 150),"webui": (120, 120),
}
4. LayoutPrompt · 動態選擇模塊
????????動態選擇模塊:從layout數據庫中選擇最符合要求的一系列layout。
????????既然是選擇,那么就一定存在選擇標準,不同任務的選擇標準也肯定是不同的。比如對于Poster任務最重要的是layout中元素類型要相同;對于產品海報最重要的是要把中間放圖片的部分給留出來用來展示產品照片,因此需要做到內容避讓。基于這樣一個前提,我們寫了很多任務背景下的layout動態選擇。當然這里的任務背景和前面的序列化生成的任務背景是相互對應的,用戶選擇任務類型后,動態選擇模塊和序列化模塊都會選擇相對應的任務類型(Layout生成模式)。
? ? ? ? 下面我們舉代碼例子,具體完整代碼后續更新在Gitee以及CSDN。
4.1 動態選擇模塊的父類
class ExemplarSelection:def __init__(self,train_data: list,candidate_size: int,num_prompt: int,shuffle: bool = True,):self.train_data = train_data # 原始訓練數據集self.candidate_size = candidate_size # 候選池最大容量self.num_prompt = num_prompt # 最終選取的示例數量self.shuffle = shuffle # 是否隨機打亂# 構建候選池:若指定大小 > 0,則隨機采樣if self.candidate_size > 0:random.shuffle(self.train_data)self.train_data = self.train_data[: self.candidate_size] # 截取前N個作為候選池def __call__(self, test_data: dict):pass# 過濾無效樣本def _is_filter(self, data):# 檢查是否存在 width/height 為 0 的無效元素return (data["discrete_gold_bboxes"][:, 2:] == 0).sum().bool().item()# 按照分數拿到對應樣本(分數計算由下面代碼實現)def _retrieve_exemplars(self, scores: list):scores = sorted(scores, key=lambda x: x[1], reverse=True)exemplars = []for i in range(len(self.train_data)):if not self._is_filter(self.train_data[scores[i][0]]):exemplars.append(self.train_data[scores[i][0]])# 達到目標數量時提前終止if len(exemplars) == self.num_prompt:breakif self.shuffle:random.shuffle(exemplars)return exemplars
- 原始的examle就是從Train_data中拿。
- 候選池就是用來放example的池子,有一個上限。
- 父類中的核心就是一個根據分數list從train_data拿example的函數。
4.2?基于元素類型相似度的動態選擇
class GenTypeExemplarSelection(ExemplarSelection):def __call__(self, test_data: dict):scores = []test_labels = test_data["labels"]for i in range(len(self.train_data)):train_labels = self.train_data[i]["labels"]score = labels_similarity(train_labels, test_labels) # 核心算法scores.append([i, score])return self._retrieve_exemplars(scores)def labels_similarity(labels_1, labels_2):def _intersection(labels_1, labels_2):cnt = 0x = Counter(labels_1)y = Counter(labels_2)for k in x:if k in y:cnt += 2 * min(x[k], y[k])return cntdef _union(labels_1, labels_2):return len(labels_1) + len(labels_2)if isinstance(labels_1, torch.Tensor):labels_1 = labels_1.tolist()if isinstance(labels_2, torch.Tensor):labels_2 = labels_2.tolist()return _intersection(labels_1, labels_2) / _union(labels_1, labels_2)
- 通過看輸入前導信息中的label和example中哪些東西的label類型相近,則選擇其作為參考。
- 核心的分數計算方法為labels_similarity。用來比較Train_data中元素和Test_data中元素的label相似度。
4.3 基于類型+尺寸相似度的的動態選擇
class GenTypeSizeExemplarSelection(ExemplarSelection):labels_weight = 0.5bboxes_weight = 0.5def __call__(self, test_data: dict):scores = []test_labels = test_data["labels"]test_bboxes = test_data["bboxes"][:, 2:]for i in range(len(self.train_data)):train_labels = self.train_data[i]["labels"]train_bboxes = self.train_data[i]["bboxes"][:, 2:]score = labels_bboxes_similarity( # 核心算法train_labels,train_bboxes,test_labels,test_bboxes,self.labels_weight,self.bboxes_weight,)scores.append([i, score])return self._retrieve_exemplars(scores)def labels_bboxes_similarity(labels_1, bboxes_1, labels_2, bboxes_2, labels_weight, bboxes_weight
):labels_sim = labels_similarity(labels_1, labels_2)bboxes_sim = bboxes_similarity(labels_1, bboxes_1, labels_2, bboxes_2)return labels_weight * labels_sim + bboxes_weight * bboxes_simdef labels_similarity(labels_1, labels_2):def _intersection(labels_1, labels_2):cnt = 0x = Counter(labels_1)y = Counter(labels_2)for k in x:if k in y:cnt += 2 * min(x[k], y[k])return cntdef _union(labels_1, labels_2):return len(labels_1) + len(labels_2)if isinstance(labels_1, torch.Tensor):labels_1 = labels_1.tolist()if isinstance(labels_2, torch.Tensor):labels_2 = labels_2.tolist()return _intersection(labels_1, labels_2) / _union(labels_1, labels_2)def bboxes_similarity(labels_1, bboxes_1, labels_2, bboxes_2, times=2):"""bboxes_1: M x 4bboxes_2: N x 4distance: M x N"""distance = torch.cdist(bboxes_1, bboxes_2) * timesdistance = torch.pow(0.5, distance)mask = labels_1.unsqueeze(-1) == labels_2.unsqueeze(0)distance = distance * maskrow_ind, col_ind = linear_sum_assignment(-distance)return distance[row_ind, col_ind].sum().item() / len(row_ind)
- 標簽相似度計算使用bboxes_similarity。相似度的比較非常簡單:相同/總數
- 尺寸相似度使用labels_similarity計算。距離計算公式較為復雜,見下面
?
-
torch.cdist(bboxes_1, bboxes_2)
:- 計算兩組邊界框之間的歐幾里得距離。
bboxes_1
?和?bboxes_2
?通常是形狀為?(M, 4)
?和?(N, 4)
?的張量,其中每個邊界框由?(x_min, y_min, x_max, y_max)
?來表示。 - 這將返回一個形狀為?
(M, N)
?的距離矩陣,每個元素表示一對邊界框之間的歐幾里得距離。
- 計算兩組邊界框之間的歐幾里得距離。
-
distance = distance * times
:- 將每個距離乘以一個縮放因子?
times
,可能是為了調整距離的影響力。
- 將每個距離乘以一個縮放因子?
-
distance = torch.pow(0.5, distance)
:- 對距離進行指數衰減,也就是將距離取 0.5 次方,實際上是對距離做了平方根處理,可能是為了減少較大距離對結果的影響。
-
mask = labels_1.unsqueeze(-1) == labels_2.unsqueeze(0)
:- 創建一個形狀為?
(M, N)
?的布爾掩碼,比較?bboxes_1
?和?bboxes_2
?中的標簽?labels_1
?和?labels_2
?是否相同。 labels_1
?和?labels_2
?假設是形狀為?(M,)
?和?(N,)
?的張量,表示每個邊界框的標簽(例如類別 ID)。- 通過?
unsqueeze(-1)
?和?unsqueeze(0)
?增加維度,使得兩個標簽張量可以進行逐元素比較,最終得到一個布爾掩碼。標簽相同的位置會為?True
,不同的位置為?False
。
- 創建一個形狀為?
-
distance = distance * mask
:- 將掩碼應用到距離矩陣上。對于標簽不匹配的邊界框對,其對應的距離值會被置為零。這樣只有標簽相同的邊界框對才會參與到后續的匹配中。
-
row_ind, col_ind = linear_sum_assignment(-distance)
:- 使用匈牙利算法(通過?
linear_sum_assignment
?函數)來求解最優匹配問題。由于?linear_sum_assignment
?是最小化代價的,而我們想要最小化距離,所以傳入的是距離的負值(即?-distance
)。 - 函數返回兩個數組,
row_ind
?和?col_ind
,分別表示最優匹配的行和列索引,即邊界框在?bboxes_1
?和?bboxes_2
?中的匹配關系。
- 使用匈牙利算法(通過?
-
return distance[row_ind, col_ind].sum().item() / len(row_ind)
:- 根據最優匹配的行和列索引選擇相應的距離值,求和后除以匹配的數量(
len(row_ind)
),得到匹配的平均距離。 .item()
?將張量轉為 Python 標量值。
- 根據最優匹配的行和列索引選擇相應的距離值,求和后除以匹配的數量(
5. LayoutPrompt ·排名模塊
class Ranker:lambda_1 = 0.2lambda_2 = 0.2lambda_3 = 0.6def __init__(self, val_path=None):self.val_path = val_pathif self.val_path:self.val_data = read_pt(val_path)self.val_labels = [vd["labels"] for vd in self.val_data]self.val_bboxes = [vd["bboxes"] for vd in self.val_data]def __call__(self, predictions: list):metrics = []for pred_labels, pred_bboxes in predictions:metric = []_pred_labels = pred_labels.unsqueeze(0)_pred_bboxes = convert_ltwh_to_ltrb(pred_bboxes).unsqueeze(0)_pred_padding_mask = torch.ones_like(_pred_labels).bool()metric.append(compute_alignment(_pred_bboxes, _pred_padding_mask))metric.append(compute_overlap(_pred_bboxes, _pred_padding_mask))if self.val_path:metric.append(compute_maximum_iou(pred_labels,pred_bboxes,self.val_labels,self.val_bboxes,))metrics.append(metric)metrics = torch.tensor(metrics)min_vals, _ = torch.min(metrics, 0, keepdim=True)max_vals, _ = torch.max(metrics, 0, keepdim=True)scaled_metrics = (metrics - min_vals) / (max_vals - min_vals)if self.val_path:quality = (scaled_metrics[:, 0] * self.lambda_1+ scaled_metrics[:, 1] * self.lambda_2+ (1 - scaled_metrics[:, 2]) * self.lambda_3)else:quality = (scaled_metrics[:, 0] * self.lambda_1+ scaled_metrics[:, 1] * self.lambda_2)_predictions = sorted(zip(predictions, quality), key=lambda x: x[1])ranked_predictions = [item[0] for item in _predictions]return ranked_predictions
lambda_1
,?lambda_2
,?lambda_3
?是三個常量,分別代表加權系數,用于在排名時對不同指標的加權。這些系數的值總和為 1,用來平衡不同的評估標準。val_path
?是一個可選參數,用來指定驗證數據集的路徑。如果提供了路徑,它會讀取驗證數據(通過?read_pt
?函數),并從中提取出標簽 (val_labels
) 和邊界框 (val_bboxes
) 信息。val_labels
?和?val_bboxes
?是從驗證數據集中提取的標簽和邊界框,用于后續的計算(如 IOU 等)。-
對于每個預測 (
pred_labels
,pred_bboxes
),首先進行一些預處理:unsqueeze(0)
:增加一個維度,使得張量的形狀符合后續操作的要求。convert_ltwh_to_ltrb(pred_bboxes)
:將邊界框格式從?ltwh
(左上角坐標和寬高)轉換為?ltrb
(左上角和右下角坐標)。- 創建一個全為?
True
?的填充掩碼?_pred_padding_mask
。
-
之后,計算兩個指標:
compute_alignment
:計算預測邊界框的對齊度(如何與真實邊界框對齊)。compute_overlap
:計算預測邊界框的重疊度(預測與真實框的交集比重)。
-
如果提供了驗證數據(
self.val_path
),還會計算compute_maximum_iou
,這是計算預測邊界框與真實邊界框的最大 IOU(交并比)值。 - 將所有指標值(存儲在?
metrics
?列表中)轉換為張量,并對每個指標進行最小-最大標準化。這樣可以確保每個指標的值都在?[0, 1]
?范圍內,便于后續的加權處理。 -
如果有 IOU 指標,
quality
的計算會涉及三項指標:scaled_metrics[:, 0]
:對齊度scaled_metrics[:, 1]
:重疊度scaled_metrics[:, 2]
:最大 IOU- 對 IOU 進行反向處理?
(1 - scaled_metrics[:, 2])
,表示較高的 IOU 值意味著較好的預測。
-
如果沒有 IOU,則只使用對齊度和重疊度。
- 將預測結果與其質量評分進行打包,并按質量評分進行排序。
- 返回排序后的預測列表?
ranked_predictions
,從質量最好的預測到最差的預測。
6. 總結
本篇文章帶大家深入了解了PosterGenius項目的Layout生成部分的第一篇,后續將更新Layout系列的第二篇。歡迎大家繼續支持貓貓呀!!
?【如果想學習更多深度學習文章,可以訂閱一下熱門專欄】
- 《PyTorch科研加速指南:即插即用式模塊開發》_十二月的貓的博客-CSDN博客
- 《深度學習理論直覺三十講》_十二月的貓的博客-CSDN博客
- 《AI認知筑基三十講》_十二月的貓的博客-CSDN博客
如果想要學習更多pyTorch/python編程的知識,大家可以點個關注并訂閱,持續學習、天天進步你的點贊就是我更新的動力,如果覺得對你有幫助,辛苦友友點個贊,收個藏呀~~~
本文撰寫人:十二月的貓? ? ?十二月的貓-CSDN博客