[論文心得] Scaling Up to Excellence: Practicing Model Scaling for Photo-Realistic Image Restoration In the Wild

論文名稱: Scaling Up to Excellence: Practicing Model Scaling for Photo-Realistic Image Restoration In the Wild

image-20240126105037706

Scaleing Up to Excellence: 令人驚艷圖像恢復論文

提出 SUPIR (Scaling-UP Image Restoration),主要有以下方法:

  • 透過 prompt 提升修復能力,甚至透過 Negtive Prompt 來加強
  • 超過 2000 萬張超高畫質訓練素材 。

結果在品質上可以看到相當好的成效。

論文: https://arxiv.org/abs/2401.13627

網站: https://supir.xpixel.group/

image-20240126105147093

快速總結

  • 研究主題:使用生成式先驗和模型縮放的技術,實現針對真實場景的超高質量的圖像恢復方法。
  • 數據集:收集了2000萬張高分辨率、高質量的圖像,每張圖像都有描述性的文本註釋,用於模型的訓練。
  • 創新點:提出了一種通過文本提示來引導圖像恢復的方法,擴展了圖像恢復的應用範圍和潛力。並且引入了負質量提示來進一步提高感知質量。還開發了一種恢復引導採樣方法,以抑制生成式恢復中遇到的保真度問題。
  • 實驗結果:展示了SUPIR的卓越的圖像恢復效果,以及其通過文本提示進行恢復操作的新穎能力。

系統架構

image-20240126105141343

EDM Sampler with Restoration Guidance 的主要內容如下:

  • EDM Sampler with Restoration Guidance 是一種用於圖像修復的採樣方法,它基於擴散模型的原理,並引入了一個新的超參數 τr 來控制採樣過程中對低質量圖像的引導程度。
  • 目的:該方法的目的是在保持圖像的感知質量的同時,提高圖像的忠實度,即使圖像在採樣過程中不會偏離低質量圖像的內容。
  • 原理:該方法的原理是在每一步的採樣中,根據 τr 的大小,將預測的結果 zt−1 與低質量圖像 zLQ 進行一定比例的混合,使得 zt−1 在保留生成的細節和紋理的同時,也接近於 zLQ。
  • 效果:該方法的效果通過在不同的合成和真實的低質量圖像上的實驗進行了驗證,並與其他的圖像修復方法進行了比較。結果顯示,該方法在非參考指標上取得了最好的表現,並在全參考指標上也有不錯的表現。此外,該方法還可以通過調整 τr 的大小,實現對圖像修復的靈活控制,從而達到不同的效果。

[Golang][Notion] 如何透過 Golang 來操控 Notion DB 當成線上資料庫

Notion Databases: An advanced tutorial on Notion's game-changing feature

前提

在撰寫許多 Side Project 的時候,除了網路服務伺服器之外,最困擾的大概就是資料庫的問題。雖然之前我的文章 [學習心得][Golang] 把 Github Issue 當成資料庫來用 曾經教過透過 Github Golang API 來將簡單的一些資料放在 Github Issue 上,但是如果資料格式比較複雜的時候。可能就會需要透過類似資料庫格式的儲存體來處理。 偏偏許多線上資料庫都是算時間與用量,對於想寫一些有趣的 Side Project 卻沒有那麼友善。

本篇文章將使用 Notion Database 作為資料的儲存體,並且透過 Golang 去查詢,插入相關的資料處理。 本篇文章也會從如何設定一個 Notion Integration 開始教導,讓你透過 Golang 來操控 Notion Database 沒有任何痛苦。

本篇文章將透過: https://github.com/kkdai/linebot-smart-namecard 來說明。

關於 Notion Database

以上是一段 Notion 官方教學影片 Notion Database 裡面有提到如何建立一個 Database ,並且有稍微解釋:

  • Create -> 選擇 Database 欄位中 -> Table

使用 Notion Database 的好處:

image-20240115211147923

  • Notion Database 支援相當豐富的格式,並且有很漂亮的視覺化介面。
  • 並且 Notion Database 支援多種格式: Table, Board, Calendar, List, and Gallery
  • 除了蠻方便 coding 之外,如果有後台管理員,可以透過 Notion UI 來直接查看結果。

建立 Notion Integration

可以先到 Notion Developer 建立第一個 Notion Integration :

image-20240115233258526

  • Type: Internal 只有你可以用,其他人沒有辦法選到。
  • Name: 只要可以辨識就好。

這樣就可以建立了 Integration ,並且取的 Internal Integration Secret (Notion API Key):

image-20240115233433452

開啟 Notion Database 讓 Notion Integration 可以存取:

記得要讓 Notion 頁面取得 Integration 權限 ,參考以下圖片。

官方給的 GIF 檔案相當的清楚,這也是最重要的其中一步。要讓你的資料可以讓 Integration 存取。

取得 Notion Database ID

這也是一個相當重要的事情,要使用 Golang 去存取你的 Notion Database 就需要以下兩個資料:

  • Notion Internal Integration Secret (API Key)
  • Notion Page ID

Notion DB 的頁面網址應該是 https://www.notion.so/b764xxxxxa?v=dexxxx1 那麼 b764xxxxxa就是你的 DatabasePageId。

了解 Notion Database Data Type:

在準備要連接 Notion Database 的時候,你必須要先知道每個欄位的差別。

image-20240115234749333

以我的資料庫為例子:

  • UID: 存放 LINE OA User UID,做為辨識之用。資料格式是: Text

image-20240115234848362

  • 其他都是 Title, Address, Email, Phone Number 。
  • Name: 使用了 Title 這個資料格式,其中差別為: Title 只能有一個欄位,並且會變成新頁面的標題。

image-20240115235405588

開始撰寫 Golang Notion Database 程式碼:

這邊使用的套件是: https://github.com/jomei/notionapi

先了解資料架構

// Person 定義了 JSON 資料的結構體
type Person struct {
	Name        string `json:"name"`
	Title       string `json:"title"`
	Address     string `json:"address"`
	Email       string `json:"email"`
	PhoneNumber string `json:"phone_number"`
}

// DatabaseEntry 定義了 Notion 資料庫條目的結構體。
type NotionDB struct {
	DatabaseID string
	Token      string
}
  • Person: 來自名片掃描的 JSON 資料,也代表這裡每個欄位的資料。除了 UID 是要透過參數進來的。
  • NotionDB:啟動 Notion 需要知道的資料:
    • Token: 就是 Notion Integration Secret
    • DatabaseID: 在 URL 即可取得。 Notion DB 的頁面網址應該是 https://www.notion.so/b764xxxxxa?v=dexxxx1 那麼 b764xxxxxa就是你的 DatabasePageId。
// QueryDatabase 根據提供的屬性和值查詢 Notion 資料庫。
func (n *NotionDB) QueryDatabase(UId, property, value string) ([]Person, error) {
	client := notionapi.NewClient(notionapi.Token(n.Token))

	// Add UId to the filter conditions
	// 建立查詢過濾條件
	filter := &notionapi.DatabaseQueryRequest{
		Filter: notionapi.AndCompoundFilter{
			notionapi.PropertyFilter{
				Property: property,
				RichText: &notionapi.TextFilterCondition{
					Equals: value,
				},
			},
			notionapi.PropertyFilter{
				Property: "UID",
				RichText: &notionapi.TextFilterCondition{
					Equals: UId,
				},
			},
		},
	}

	// 調用 Notion API 來查詢資料庫
	result, err := client.Database.Query(context.Background(), notionapi.DatabaseID(n.DatabaseID), filter)
	if err != nil {
		return nil, err
	}

	var entries []Person

	for _, page := range result.Results {
		entry := n.createEntryFromPage(&page)
		entries = append(entries, entry)
	}
	return entries, nil
}

這一段需要注意的是:

  • 過濾條件使用的是 AndCompoundFilter ,也就是要兩個條件 A && B 。
  • 其中要注意的 PropertyFilter如果資料格式不同的時候,需要處理不同資料。
    • Text: TextFilterCondition
    • Title: TitleFilterCondition
  • 依此類推。

再來看如何新增資料

// AddPageToDatabase adds a new page with the provided field values to the specified Notion database.
func (n *NotionDB) AddPageToDatabase(Uid string, name string, title string, address string, email string, phoneNumber string) error {
	client := notionapi.NewClient(notionapi.Token(n.Token))

	// 建立 Properties 物件來設置頁面屬性
	properties := notionapi.Properties{
		"UID": notionapi.RichTextProperty{
			RichText: []notionapi.RichText{
				{
					PlainText: name,
					Text:      &notionapi.Text{Content: Uid},
				},
			},
		},
		"Name": notionapi.TitleProperty{
			Title: []notionapi.RichText{
				{
					PlainText: name,
					Text:      &notionapi.Text{Content: name},
				},
			},
		},
		// Address, Email, Phone Number....
	}

	// 創建一個新頁面的請求
	pageRequest := &notionapi.PageCreateRequest{
		Parent: notionapi.Parent{
			DatabaseID: notionapi.DatabaseID(n.DatabaseID),
		},
		Properties: properties,
	}

	// 調用 Notion API 來創建新頁面
	_, err := client.Page.Create(context.Background(), pageRequest)
	if err != nil {
		log.Println("Error creating page:", err)
		return err
	}

	log.Println("Page added successfully:", Uid, name, title, address, email, phoneNumber)
	return nil
}
  • 大部分程式碼都是類似的,但是根據欄位不同。需要調整以下內容:
"UID": notionapi.RichTextProperty{
			RichText: []notionapi.RichText{
				{
					PlainText: name,
					Text:      &notionapi.Text{Content: Uid},
				},
			},
		},
  • 其中的 name 是固定參數,不能改。
  • 只有後面的 Content: Uid 可以改。

  • 此外,根據欄位不同 RichTextProperty 也會變動。如果不正確,就無法正確地寫入資料。

最後測試範例程式碼:

func TestAddNotionDB(t *testing.T) {
	token := os.Getenv("NOTION_INTEGRATION_TOKEN")
	pageid := os.Getenv("NOTION_DB_PAGEID")

	// If not set token and pageid , skip this test
	if token == "" || pageid == "" {
		t.Skip("NOTION_INTEGRATION_TOKEN or NOTION_DB_PAGEID not set")
	}

	db := &NotionDB{
		DatabaseID: pageid,
		Token:      token,
	}

	err := db.AddPageToDatabase("uid", "name", "title", "address", "[email protected]", "phone")
	if err != nil {
		t.Fatal(err)
	}
}

參考資料:

[遊戲天國] FF15 全破

FF15 全破了, 同時有玩 FF16 (用 PS Portal) 跟 FF15 (Steam Deck) ,後來竟然覺得 FF15 比較讓我玩得下去。 很專心地把它破關了。

  • 一開始為人詬病的開車兜風那段,我覺得很悠閒。

  • FF15 的相當多樣的支線任務我還蠻喜歡的,不論陸行鳥,拍照任務,還是武器鍛鍊任務。

  • 最後的三個好友單挑歷代王,很感人啊。

  • 幾個 DLC 慢慢留著玩,應該要準備人龍8 惹。

找了一下劇情完結:

關於本傳的解釋:

image-20240115225004791

image-20240115225020138

[研討會心得] NV TW LLM Developer Day 2024

image-20240111122733576

活動資訊

NVIDIA LLM Developer Day重播)

  • 09:30 - 11:00 台灣時間 - 議程 1: 開發大型語言模型 (LLMs) 的快速途徑: slide

  • 11:10 - 12:50 台灣時間 - 議程 2a:量身客製自己的大型語言模型應用:slides

  • 15:30 台灣時間 - 議程 2b:生物科學大型語言模型和生成式人工智慧

  • 17:10 台灣時間 - 議程 3:運行自己的大型語言模型

議程 1: 開發大型語言模型 (LLMs) 的快速途徑

案例 (1) - 音樂公司的客服系統

image-20240111095051777image-20240111095054776

  • 第一個案例: 透過 LLM 來做樂器商的客服系統。
  • 收到客戶 email, 客戶 tag
  • 根據問題找出 RAG 可能的回覆。(顯示於下方),幫助客服回覆。

感想:

  • 竟然用 OpenAI 而非 LLAMA 以 NV DevDay 來說有點怪。
  • 裏面 OpenAI 版本是 < 1.0 , 但是 Openai package 在 11/07 就已經更新到 1.0。(因為當初還被雷到)

1.1 解讀 email 的 prompt

image-20240111095509845

  • 分條列出好的地方跟不好的地方。
  • 分類緊急性。
  • 判斷信件的口吻
  • 結構化輸出

1.2 分析的 Prompt

image-20240111095935738

  • 資料處理的 prompt

image-20240111100529570

  • 格式化輸出的 prompt

image-20240111100620053

  • 透過 COLANG 來定義使用者資料流。

尚未公開產品 Nemotron-3

image-20240111102836188

  • NeVA (NeMo Vision and Language Assistant)

其他:

GTC 2024 - 實體 GTC 03/18。

更多參考:

[打造自己的知識系統] Part 1: 整理資訊流 with IFTTT

image-20240109113904905

(From PlantUML)

總結

我一直以來都是一個喜歡東看西看的人(聽說很多人跟我一樣),但是一直以來資訊流的整理一直是我很痛苦的事情。接下來可能會有一系列的文章記錄著我邊打造個人 LLM KM 系統的時候的一些記錄跟想法。

資訊流

原本資訊流還蠻簡單的,就是希望所有的資訊可以透過 twitter 去當 gateway 然後開始轉到其他地方。 這裡有點麻煩得時候,自從 Twitter 再也不接受免費 API 的申請後(最便宜 100$USD) 。 只好去購買 IFTTT 的服務幫我把資訊從 Twitter 打到 Webhook 。 這邊我以前有類似的文章:

看到五年前開始打造的時候才 10 ~ 20 github issue ,現在卻有 1.3k 的數字。開始思考要如何整理相關資訊。

更多參考:

[線上課程筆記]DeepLearningAI - Advanced Retrieval for AI with Chroma

image-20240108172844892

課程簡介

Deep Learning AI 新的課程,如何優化 IR/RAG on Chroma 。 講師是 Chroma co-founder 有以下三個技術:

  • Query Expansion: 透過相關概念來擴展查詢。
  • Cross-encoder reranking: 透過不同檢索編碼來排序查詢結果。
  • Training and utilizing Embedding Adapters: 透過加入 adapter 來優化檢索結果。

課程資訊: https://learn.deeplearning.ai/advanced-retrieval-for-ai/

RAG Pitfall

經常查詢 RAG 結果回來會是不相關的,怎麼看出來? 透過一個 umap 套件

import umap
import numpy as np
from tqdm import tqdm

embeddings = chroma_collection.get(include=['embeddings'])['embeddings']
umap_transform = umap.UMAP(random_state=0, transform_seed=0).fit(embeddings)


# 畫點出來
import matplotlib.pyplot as plt

plt.figure()
plt.scatter(projected_dataset_embeddings[:, 0], projected_dataset_embeddings[:, 1], s=10)
plt.gca().set_aspect('equal', 'datalim')
plt.title('Projected Embeddings')
plt.axis('off')

比較相近的問題(單一問題,比較容易)

image-20240108175808801

這樣看起來查詢的資訊跟我們問得蠻相近的,紅色是回答的。綠色是前面幾個相關的。

如果問句有兩個以上,或是問句本身就不太相關。

image-20240108180009118

這樣就會出現差相當多的結果,造成查詢的資料相關度過少。出來的結果當然也就很差。

解決方式就要靠接下來的三個方法。

Query Expansion

image-20240108194721737

透過延伸的假設答案,加上原來的問題。一起下去詢問:

def augment_query_generated(query, model="gpt-3.5-turbo"):
    messages = [
        {
            "role": "system",
            "content": "You are a helpful expert financial research assistant. Provide an example answer to the given question, that might be found in a document like an annual report. "
        },
        {"role": "user", "content": query}
    ] 

    response = openai_client.chat.completions.create(
        model=model,
        messages=messages,
    )
    content = response.choices[0].message.content
    return content

e.g.

  • Q: Was there significant turnover in the executive team?
  • 先用這個 Q 直接問 OpenAI 得到可能的解答 hypothetical_answer,但是因為沒有查詢特有 RAG 資料可能不會正確。
  • 透過 f"{original_query} {hypothetical_answer}" 結合再一起,再來透過 VectorDB 來尋找答案。
original_query = "Was there significant turnover in the executive team?"
hypothetical_answer = augment_query_generated(original_query)

joint_query = f"{original_query} {hypothetical_answer}"
print(word_wrap(joint_query))

Cross-encoder reranking

image-20240108173134289

透過不同問句,取得相似問句。透過該捷達來評分。這邊的作法如下:

  • 原本問句 original_query ,得到數個 generated_queries:
  • 然後把原本問句跟其他問句下去找數組的解答。
  • 將每一組解答,透過 scores = cross_encoder.predict(pairs) 評分來打分數。
  • 挑選出比較高分的幾個項目再來去 RAG
original_query = "What were the most important factors that contributed to increases in revenue?"
generated_queries = [
    "What were the major drivers of revenue growth?",
    "Were there any new product launches that contributed to the increase in revenue?",
    "Did any changes in pricing or promotions impact the revenue growth?",
    "What were the key market trends that facilitated the increase in revenue?",
    "Did any acquisitions or partnerships contribute to the revenue growth?"
]

queries = [original_query] + generated_queries

results = chroma_collection.query(query_texts=queries, n_results=10, include=['documents', 'embeddings'])
retrieved_documents = results['documents']

# Deduplicate the retrieved documents
unique_documents = set()
for documents in retrieved_documents:
    for document in documents:
        unique_documents.add(document)

unique_documents = list(unique_documents)

pairs = []
for doc in unique_documents:
    pairs.append([original_query, doc])
    
    
print("Scores:")
for score in scores:
    print(score)

Training and utilizing Embedding Adapters

image-20240108195535753

# 產生相關問句
def generate_queries(model="gpt-3.5-turbo"):
    messages = [
        {
            "role": "system",
            "content": "You are a helpful expert financial research assistant. You help users analyze financial statements to better understand companies. "
            "Suggest 10 to 15 short questions that are important to ask when analyzing an annual report. "
            "Do not output any compound questions (questions with multiple sentences or conjunctions)."
            "Output each question on a separate line divided by a newline."
        },
    ]

    response = openai_client.chat.completions.create(
        model=model,
        messages=messages,
    )
    content = response.choices[0].message.content
    content = content.split("\n")
    return content

產生相關答案

generated_queries = generate_queries()
for query in generated_queries:
    print(query)

透過 10 ~ 15 個問題,產生衍生的答案。約有 150 個。 這個就變成是新的資料集。(RAG)

透過新的相似度比較方式 (mse_loss):

def mse_loss(query_embedding, document_embedding, adaptor_matrix, label):
    return torch.nn.MSELoss()(model(query_embedding, document_embedding, adaptor_matrix), label)

透過這個方式,再來找出最好的解答。

image-20240108202350286

這張圖可以看出 adapted query 結果不容易產生不相關的答案。這邊也有建議,如果可以拿到使用者的資料來作為 adapted 問句,可能可以讓答案變得更好。

衍生思考:

  • 發現結果過於不相關的時候。
  • 透過多問幾題,然後找出相關答案。
  • 變成新的資料集,作為查詢。就可以優化整個 RAG 的資料集,進而得到更好的解答。

課程總結:

  • 先解釋 RAG 經常會遇到的陷阱。過於分散的問句,造成相似的解答不相關,回覆就會無法準確。
  • Expanding Query: 請 OpenAI 幫你多問幾題,然後把問題跟答案都放進去詢問。
  • Cross Re-Rank: 算是上面的進化版,透過產生問句的答案。透過一個評分機制。找到比較好的答案再下去 RAG 。
  • Embedding Adapter: 產生更多問句,透過問句產生的解答。當作是新的 dataset ,並且下來 RAG 。

更多參考: