全部產品
Search
文件中心

Platform For AI:多模態資料管理和使用

更新時間:Apr 29, 2026

1. 概述

多模態資料管理針對圖片等多模態資料,可通過多模態大模型、Embedding模型等進行預先處理(智能打標、語義索引),形成豐富的中繼資料。藉助這些中繼資料,支援對多模態資料進行搜尋、篩選等操作,便於快速挖掘特定情境的資料子集,用於進一步的資料標註、訓練等流程。同時,PAI資料集還開放了全套OpenAPI,便於在自建平台中整合。產品架構如下圖所示:

image

2. 使用限制

當前PAI多模態資料管理有如下使用限制:

  • 使用地區:當前支援杭州、上海、深圳、烏蘭察布、北京、廣州、新加坡、德國、美國(維吉尼亞)、中國香港、東京、雅加達、廣州、美國(矽谷)、吉隆坡、首爾地區。

  • 儲存類型:當前僅支援在OSSObject Storage Service中使用PAI多模態資料管理。

  • 檔案類型:當前僅支援圖片類型檔案,支援檔案格式:jpg、 jpeg、png、gif、bmp、tiff、webp。

  • 檔案數量:支援單個資料集版本最大1,000,000個檔案,如有特殊需求可聯絡PAI PDSA擴容。

  • 使用模型:

    • 打標模型:支援使用百鍊平台-Qwen VL Max/Plus模型。

    • 索引模型:支援使用百鍊多模態Embedding模型(如tongyi-embedding-vision-plus),以及PAI Model Gallery的GME模型作為索引模型,部署至PAI-EAS使用。

  • 元資訊儲存:

    • 中繼資料:中繼資料安全儲存於PAI內建的中繼資料庫。

    • Embedding向量:支援儲存於下列自訂向量資料庫中:

      • Elasticsearch(向量增強版,8.17.0版本及以上)

      • OpenSearch(向量檢索版)

      • Milvus(2.4及以上版本)

      • Hologres(4.0.9以上版本)

      • Lindorm(向量引擎版本)

  • 資料集處理模式:目前支援全量模式增量模式運行智能打標任務及語義索引任務。

3. 使用流程

PAI多模態資料管理使用說明

3.1 前置準備

3.1.1 開通PAI,建立預設工作空間並擷取空間管理員權限

  1. 使用主帳號開通PAI並建立工作空間。登入PAI控制台,左上方選擇開通地區,然後一鍵授權和開通產品。

  2. 操作帳號授權。當使用主帳號操作時,可跳過此步。當使用RAM帳號時,需要具有空間管理員角色。操作帳號授權請參見管理工作空間 > 成員角色配置。

3.1.2 開通百鍊並建立API-KEY

開通阿里雲百鍊並建立API-KEY,請參考擷取API-KEY

3.1.3 建立向量資料庫

建立向量資料庫執行個體

多模態資料集管理目前支援以下幾種阿里雲向量資料庫:

  • Elasticsearch(向量增強版,8.17.0版本及以上)

  • OpenSearch(向量檢索版)

  • Milvus(2.4及以上版本)

  • Hologres(4.0.9以上版本)

  • Lindorm(向量引擎版本)

各個雲向量資料庫執行個體建立請參考對應雲產品文檔。

網路設定以及白名單配置

  • 公網方式

    若向量庫執行個體開通了公網地址,將下面的IP列表添加到執行個體的公網訪問白名單地址清單後,多模態資料管理服務即可通過公網訪問此執行個體。Elasticsearch白名單設定請參見配置執行個體公網或私網訪問白名單

    地區

    IP列表

    杭州

    47.110.230.142、47.98.189.92

    上海

    47.117.86.159、106.14.192.90

    深圳

    47.106.88.217、39.108.12.110

    烏蘭察布

    8.130.24.177、8.130.82.15

    北京

    39.107.234.20、182.92.58.94

  • 私網方式

    請提交工單申請。

建立向量索引表(可選)

系統提供自動建立索引表功能,如果您無需自訂建立索引表,可跳過此步驟。

在某些向量資料庫中,向量索引表也稱為Collection或Index。

索引表結構定義(表結構必須遵循如下定義):

表結構定義

{
    "id":"text",                    //主鍵id,在OpenSearch中需要定義,其他資料庫中預設存在,無需定義
    "index_set_id": "keyword",      //索引集ID,需支援索引
    "file_meta_id": "text",         //檔案中繼資料ID   
    "dataset_id": "text",           //資料集ID
    "dataset_version": "text",      //資料集版本
    "uri": "text",                  //OSS檔案的uri
    "file_vector": {                //向量欄位
        "type": "float",            //向量類型:浮點型
        "dims": 1536,               //向量維度,自訂
        "similarity": "DotProduct"  //向量距離演算法,餘弦距離或點積距離
    }
}

本文中以Elasticsearch為例通過Python建立語義索引表(其他類型向量資料庫索引表的建立請參考對應雲產品使用文檔)。範例程式碼如下:

Elasticsearch建立語義索引表示例代碼

from elasticsearch import Elasticsearch

# 1. 串連阿里雲 Elasticsearch 執行個體,
# 注意:
# (1)需要安裝3.9以上python版本:python3 -V
# (2)elasticsearch用戶端版本需安裝8.x版本:pip show elasticsearch  !!!!
# (3)如果使用vpc地址,調用方需跟es執行個體的vpc打通。否則使用公網串連地址並配置調用方公網IP到es白名單。
# 預設的userName為 elastic
es_client = Elasticsearch(
    hosts=["http://es-cn-l4p***5z.elasticsearch.aliyuncs.com:9200"],
    basic_auth=("{userName}", "{password}"),
)

# 2. 定義索引名稱和結構,預設採用HNSW索引演算法
index_name = "dataset_embed_test"
index_mapping = {
    "settings": {
        "number_of_shards": 1,          # 表分區數
        "number_of_replicas": 1         # 表副本數
    },
    "mappings": {
        "properties": {
            "index_set_id": {
                "type": "keyword"
            },
            "uri": {
                "type": "text"
            },
            "file_meta_id": {
                "type": "text"
            },
            "dataset_id": {
                "type": "text"
            },
            "dataset_version": {
                "type": "text"  
            },
            "file_vector": {
                "type": "dense_vector",  # 定義file_vector為密集向量類型
                "dims": 1536,  # 向量維度為 1536
                "similarity": "dot_product"  # 相似性計算方法為點積
            }
        }
    }
}

# 3. 建立索引
if not es_client.indices.exists(index=index_name):
    es_client.indices.create(index=index_name, body=index_mapping)
    print(f"Index {index_name} create success!")
else:
    print(f"Index {index_name} existed, do not create repeatedly.")

# 4. 查看建立的索引表結構(可選)
# indexes = es_client.indices.get(index=index_name)
# print(indexes)

3.2 建立資料集

  1. 進入PAI工作空間,在左側功能表列單擊AI资产管理 > 数据集 > 新建数据集,進入資料集配置頁面。

    image

  2. 配置資料集參數,關鍵參數如下,其他參數預設即可。

    1. 存储类型:Object Storage Service;

    2. 类型高级型

    3. 内容类型图片

    4. OSS路径:選擇資料集的OSS儲存路徑。如果您沒有準備資料集,可以下載樣本資料集retrieval_demo_data,並上傳至OSS,體驗多模態資料管理功能。

    說明

    此處匯入檔案/檔案夾,僅在系統記錄中設定了路徑,不會複製資料。

    image

    然後單擊确定,建立資料集。

3.3 建立串連

3.3.1 建立智能打標模型串連

  1. 進入PAI工作空間,在左側功能表列單擊AI资产管理 > 连接 > 模型服务 > 新建连接,開啟建立串連頁面。

    image

  2. 選擇百炼大模型服务,並配置百鍊api_key

    image

  3. 建立成功後,在列表頁可以看到建立的百鍊大模型服務。

    image

3.3.2 建立語義索引模型串連

  1. 如果您需要使用百鍊語義索引模型服務可跳過此步驟。在左側功能表列單擊Model Gallery,找到並部署GME多模態檢索模型,得到一個EAS服務。部署大約需要5分鐘,當處於运行中時,代表部署成功。

    重要

    當您不需要使用該索引模型時,可停止和刪除該服務,以免繼續產生費用。

    image

  2. 進入PAI工作空間,在左側功能表列單擊AI资产管理 > 连接 > 模型服务 > 新建连接,開啟建立串連頁面。

  3. 根據您選擇百鍊的語義索引模型還是自訂部署的EAS語義索引模型,配置模型串連資訊。

    使用百鍊語義索引模型

    • 连接类型:選擇通用多模态Embedding模型服务

    • 服务提供方:選擇第三方服務模型

    • 模型名稱tongyi-embedding-vision-plus。

    • base_url:https://dashscope.aliyuncs.com/api/v1/services/embeddings/multimodal-embedding/multimodal-embedding

    • api_key擷取API-KEY並填寫。

    image

    使用自訂部署的EAS語義索引模型

    • 连接类型:選擇通用多模态Embedding模型服务

    • 服务提供方:選擇PAI-EAS模型服务

    • EAS服务:選擇剛部署的GME多模態檢索模型。服務提供者如果不在當前帳號下,可選擇第三方模型服務。

    image

    image

  4. 建立成功後,在列表頁可以看到建立的模型串連服務。

    image

3.3.3 建立向量資料庫連接

  1. 在左側功能表列單擊AI资产管理 > 连接 > 数据库 > 新建连接,開啟建立串連頁面。

    image

  2. 多模態檢索服務支援 Milvus/Lindorm/OpenSearch/Elasticsearch/Hogress 向量資料庫,這裡以Elasticsearch為例建立串連。選擇检索分析服务-Elasticsearch,配置uriusernamepassword等資訊,詳細配置請參見建立資料庫連接

    image

    各向量資料庫連結格式參考:

    Milvus

    uri: http://xxx.milvus.aliyuncs.com:19530 
    database: {your_data_base} 
    token: root:{password}

    OpenSearch

    uri: http://xxxx.ha.aliyuncs.com
    username: {username} 
    password: {password}

    Hologres

    host: xxxx.hologres.aliyuncs.com
    database: {your_data_base} 
    port: {port}
    access_key_id={password}

    Elasticsearch

    uri: http://xxxx.elasticsearch.aliyuncs.com:9200
    username: {username} 
    password: {password}

    Lindorm

    uri: xxxx.lindorm.aliyuncs.com:{port}
    username: {username} 
    password: root:{password}
  3. 建立成功後,在列表頁可以看到建立的向量資料庫連接。

    image

3.4 建立智能打標任務

3.4.1 建立智能標籤定義

在左側功能表列單擊AI资产管理 > 数据集 > 智能标签定义 > 新建智能标签定义,開啟標籤配置頁面,配置樣本如下:

  • 引导提示词:作為一個擁有多年駕駛經驗的老司機,你有著非常豐富的高速以及城市道路駕駛經驗。

  • 标签定义

    自動駕駛樣本標籤定義

    {
        "反光貼條": " 通常為黃色,或者黃黑間隔,貼在牆角等永久的突出障礙物上,用於提示車主避讓。為條帶狀,不是錐筒,不是地鎖,不是水馬!",
        "地鎖": "也叫車位鎖,升起時可以阻止車位被佔用。存在地鎖時,務必輸出地鎖為升起還是降下狀態。有升起架子時為升起狀態,否則為降下狀態。",
        "亮燈的工程車": "有左右2個箭頭狀燈光的,並且亮起的,就是目標,否則不存在;",
        "側翻的車輛": "車輛側翻在地;",
        "倒地的水馬": "水馬是一種用於分割路面或形成阻擋的塑制殼體障礙物,一般為紅色塑料牆形態。一般用於道路交通設施,在高速路、城市道路、及天橋街道路口常見。比錐筒顯著大,且為片狀結構。水馬正常為直立,臥倒在地的話需要明確指出。",
        "倒地的錐桶": "又稱錐形交通路標、雪糕筒,俗稱路錐、三角錐,為錐形臨時道路標示。杆狀,片狀的障礙物不是錐筒,因為其不是錐形。錐筒可能被車撞倒,若圖片中存在錐筒,而又需要判斷是否為倒地時,可以觀察錐筒底部(圓錐的錐底)是否與地面接觸,如接觸則不為倒地,否則為倒地。",
        "充電車位": "靠牆且明顯帶著充電槍,充電樁裝置的,或者寫著新能源車位的為充電車位,只可能出現在停車場(室內室外均可能),注意地鎖跟充電無關。",
        "減速帶": "一般為黃黑相間,或者黃色,為橫在路上垂直於路邊緣的窄條突起,用於車輛減速。不可能出現在停車位內。",
        "減速車道線": " 車道兩側魚骨狀虛線,在實線內測,2側均有才為減速車道線。",
        "匝道": "只有明確看到高速路上的大彎道,一般匝道都在高速路幹道的右側,進出收費站才可判定存在。",
        "地面陰影": "地面有明顯的影子的情況。",
        "多雲": "只有可以看到天空,且天空有明顯的雲彩的情況才可判定存在。",
        "炫光的車": "前方燈光發生炫光(燈光由單點變成了線條狀光線)情況,通常在夜晚或者下雨天時導致。",
        "左轉、右轉、迴轉箭頭": "車道地面上所畫的乳白色箭頭標誌(少數為黃色),不是指高速路的綠白色指示道路右彎的綠白箭頭。判斷是否存在這些箭頭時,首先只有車道地面中間的明晰的箭頭標誌才是目標,其他的路邊的等一律不是。若地面存在箭頭,判斷箭頭朝向方法,右轉箭頭為從箭頭根部到箭頭尖部為順時針旋轉;左轉箭頭為從箭頭根部到箭頭尖部為類似逆時針旋轉;U型的箭頭為調頭箭頭。",
        "斑馬線": "僅可能在路面(停車場內也可能),路口存在,一定為白色線條重複間隔分布平行於路邊,用於行人通行。不可能出現在高速路,高速匝道,隧道中。",
        "曝光": " 白天,陽光直射導致鏡頭曝光(只可能發生在白天)。",
        "機動車": "視野中有其他機動車輛。",
        "匯入匯出": " 高速路多條並未一條,或者一條分為多條路的地方。",
        "路口": " 路口,且路口內沒有車道線的情況(指路口段內無,路口之外有沒有影響)。",
        "禁停牌": " 懸掛或者立在地上的牌子,寫著禁止停車文字,或者繪有圓圈包圍的P加斜線的標識。",
        "車道線": " 路上的車道線,尤其關注車道線模糊的情況。",
        "道路上掉落的石頭、輪胎": " 路上的影響通車的障礙物。",
        "隧道": " 進隧道口,出隧道口尤其注意分辨。",
        "雨天地面潮濕": "下雨天地面濕滑情況。",
        "非機動車": "包括單車,電動車,輪椅,單輪車,超市推車等非機動車物體,可能被停放在路邊,車位,行駛在路上等。"
      }

3.4.2 建立智能打標離線任務

  1. 單擊自定义数据集,單擊資料集名稱進入詳情頁面,然後再單擊数据集任务

    image

  2. 進入任務頁面,單擊新建任务 > 智能打标,並配置任務參數。

    image

    • 数据集版本:選擇需要打標的版本如v1。

    • 智能打标模型连接:選擇建立的百鍊模型串連。

    • 智能打标模型:支援千問VL-MAX和千問VL-Plus。

    • 最大并发数:最大並發數根據EAS模型服務規格配置,單卡建議最大並發為5。

    • 智能标签定义:選擇剛建立的智能標籤定義。

    • 打标模式:支援增量打標和全量打標。

  3. 智能打標任務建立成功後,在工作清單可以看到建立的打標任務。觀察啟動的智能打標任務,可點擊列表右側連結查看日誌或停止打標任務。

    說明

    初次開機智能打標任務,將進行中繼資料的構建,所需時間可能較長,請耐心等待。

3.5 建立語義索引任務

  1. 單擊資料集名稱進入詳情頁面,在索引库配置地區,單擊編輯按鈕。

    image

  2. 配置索引庫。

    • 索引模型连接:選擇3.3.2中建立的索引模型串連;

    • 索引数据库连接:選擇3.3.3中建立的索引庫串連;

    • 索引表:輸入建立向量索引表(可選)中建立的索引表名稱,即:dataset_embed_test;

    單擊保存 > 立即刷新。然後會建立一個所選資料集版本的語義索引任務,對版本中全量檔案更新語義索引。可單擊資料集詳情頁面右上方语义索引任务查看任務詳情。

    說明

    初次開機語義索引任務,將進行中繼資料的構建,所需時間可能較長,請耐心等待。

    如果沒有單擊立即重新整理,而是取消,您可以手動建立任務。詳細操作如下:

    在資料集詳情頁面單擊数据集任务進入任務頁面。

    image

    單擊新建任务 > 语义索引,配置資料集版本,最大並發數根據EAS模型服務規格配置,單卡建議最大並發為5。然後單擊確認建立語義索引任務。

    image

3.6 資料預覽

  1. 待智能打標和語義索引任務完成後,在資料集詳情頁面,單擊查看数据可預覽該資料集版本內的圖片。

    image.png

  2. 在查看資料頁面,可對該資料集版本內的圖片進行預覽,可切換“畫廊視圖”和“列表視圖”查看。

    image.png

    image.png

  3. 點擊具體圖片,可查看大圖,並查看圖片中包含的標籤。

    image.png

  4. 點擊縮圖左上方的Checkbox,可進行多選。或按住Shift鍵點擊Checkbox可一次性選擇多行資料。

    image.png

3.7 基礎資料搜尋(組合檢索)

  1. 在“查看資料”介面的左側工具列內,可進行索引检索标签搜索,按下Enter或單擊搜索即可開始搜尋。

  2. 索引检索,文本關鍵詞搜尋:基於“語義索引”的結果,通過關鍵詞與圖片索引結果的向量匹配進行搜尋。在“進階設定”中可以設定topk、Score 閾值等參數。

    image

  3. 索引检索,以圖搜圖:基於“語義索引”的結果,使用者可以從本地上傳圖片或者選擇OSS中的圖片,與資料集圖片索引結果的向量匹配進行搜尋。在“進階設定”中可以設定topk、Score閾值等參數。

    image

  4. 标签搜索:基於“智能打標”的結果,通過關鍵詞與圖片標籤的匹配進行搜尋。可同時按照包含以下任意标签(OR)同时包含以下标签(AND)排除以下任意标签(NOT)的邏輯進行搜尋。

    image

  5. 元数据搜尋:可以按照檔案名稱、儲存路徑、檔案最後修改時間進行搜尋。

    image

    以上所有搜尋條件為AND關係。

3.8 進階資料搜尋(DSL)

高级检索可以使用DSL检索。DSL是一種用於表達複雜檢索條件的領特定領域語言。它支援分組、布爾邏輯(AND/OR/NOT)、範圍比較(>, >=, <, <=)、屬性存在性(HAS/NOT HAS)、分詞匹配(:)與精確匹配(=)等,適用於進階檢索情境。文法說明詳見:擷取資料集檔案中繼資料列表

image

3.9 搜尋結果集的匯出

說明

此步驟的目的,是將搜尋結果匯出為檔案清單索引,用於後續的模型訓練或資料分析。

檢索完成後,可以單擊頁面下方导出搜索结果按鈕,支援兩種匯出模式:

image

3.9.1 匯出為檔案

  1. 單擊导出为文件,在配置頁設定匯出內容及目標OSS目錄,單擊确定

    image.png

  2. 查看匯出進度可通過在左側功能表列單擊AI资产管理 > 任务 > 数据集任务查看。

  3. 使用匯出結果。匯出後,可將匯出結果檔案與原資料集掛載至對應的訓練環境(如DLC或DSW執行個體),通過代碼實現讀取匯出結果檔案索引,並從原資料集中載入目標檔案進行模型訓練或分析。

3.9.2 匯出至邏輯型資料集版本

可以將進階型資料集的某次檢索結果匯入到另一個邏輯型資料集的版本中,後續可通過資料集的SDK來使用該邏輯型資料集的版本資料。

  1. 單擊导出至逻辑型数据集版本,選擇目標邏輯型資料集,單擊确认

    image.png

    如無可選邏輯型資料集,可參考如下內容:

    建立邏輯型資料集

    建立邏輯型資料集。在左側功能表列單擊AI资产管理 > 数据集 > 新建数据集,然後配置如下關鍵參數,其他參數按需配置即可:

    • 数据集类型:選擇逻辑型

    • 元数据OSS路径:選擇一個匯出的OSS路徑

    • 元数据导入方式:選擇稍后导入

    單擊确定建立資料集。

  2. 使用邏輯型資料集。匯入任務完成後,目標邏輯型資料集內已包含了本次匯出的中繼資料,可通過SDK來載入和使用。可在資料集的詳情頁查看SDK的使用方法。

    image

    image

    SDK的安裝命令為:

    pip install https://pai-sdk.oss-cn-shanghai.aliyuncs.com/dataset/pai_dataset_sdk-1.0.0-py3-none-any.whl

4. 自訂語義索引模型(可選)

您可以通過微調自訂語義檢索模型,在EAS部署成功後,可以按照3.3.2中的步驟建立模型串連,在後續的多模態資料管理中使用。

4.1 資料準備

本文提供了樣本資料retrieval_demo_data,您可以單擊下載。

4.1.1 資料格式要求

每個資料樣本以一行JSON格式儲存到dataset.jsonl檔案中,必須包含以下欄位:

  • image_id: 映像唯一識別碼(如圖片名稱或唯一ID)。

  • tags: 與該映像關聯的文字標籤列表,標籤為字串數組。

樣本格式:

{  
    "image_id": "c909f3df-ac4074ed",  
    "tags": ["銀色的轎車", "白色的SUV", "城市街道", "下雪", "夜晚"], 
}

4.1.2 檔案組織圖

將所有影像檔放入一個檔案夾(images),並將dataset.jsonl檔案放在與影像檔夾同級的目錄中。

目錄樣本:

├── images
│   ├── image1.jpg
│   ├── image2.jpg
│   └── image3.jpg
└── dataset.jsonl  
重要

務必使用原始檔案名dataset.jsonl,檔案夾名images不可更改。

4.2 模型訓練

  1. 在 Model Gallery 中找到檢索相關的模型,根據所需的模型大小和計算資源,選擇合適的模型來進行微調和部署。

    image

    微調 VRAM bs=4

    微調(4*A800)train_samples/second

    部署 VRAM

    向量維度

    GME-2B

    14G

    16.331

    5G

    1536

    GME-7B

    35G

    13.868

    16G

    3584

  2. 以訓練GME-2B模型為例,單擊训练,填入資料地址 (預設地址即為樣本資料地址),填寫模型輸出路徑,即可開始訓練模型。

    image

    image

4.3 模型部署

訓練完的模型可以訓練任務中,點擊部署來部署微調後的模型

點擊Model Gallery模型選項卡的部署按鈕,即可部署原始的GME模型。

image

部署完成後,可在頁面中獲得對應的 EAS 访问地址Tokenimage

4.4 模型服務調用

輸入參數

名稱

類型

是否必填

樣本值

描述

model

String

pai-multimodal-embedding-v1

模型類型,後續可以添加使用者自訂模型的支援 / 進行基模型的版本迭代

contents.input

list(dict) or list(str)

input = [{'text': text}]

input=[xxx,xxx,xxx,...]

input = [{'text': text},{'image', f"data:image/{image_format};base64,{image64}"}]

待embedding的內容。

當前只支援 text, image

輸出參數

名稱

類型

樣本值

描述

status_code

Integer

200

http狀態代碼。

200 請求成功

204 請求部分成功

400 請求失敗

message

list(str)

['Invalid input data: must be a list of strings or dict']

報錯資訊

output

dict

見下表

embedding結果

dashscope 返回結果是一個 {'output', {'embeddings': list(dict), 'usage': xxx, 'request_id':xxx}}(暫時不用 'usage', 'request_id')

embeddings 的元素包含以下key (失敗的index 會把錯誤原因加在message中)

名稱

類型

樣本值

描述

index

資料id

0

http狀態代碼。

200、400、500等

embedding

List[Float]

[0.0391846,0.0518188,.....,-0.0329895,

0.0251465]

1536

embedding後的向量

type

String

"Internal execute error."

錯誤資訊

調用範例程式碼

import base64
import json
import os
import sys
from io import BytesIO

import requests
from PIL import Image, PngImagePlugin
import numpy as np

ENCODING = 'utf-8'

hosts = 'EAS URL'
head = {
    'Authorization': 'EAS TOKEN'
}

def encode_image_to_base64(image_path):
    """
    將影像檔編碼為 Base64 字串
    """
    with open(image_path, "rb") as image_file:
        # 讀取影像檔的位元據
        image_data = image_file.read()
        # 編碼為 Base64 字串
        base64_encoded = base64.b64encode(image_data).decode('utf-8')
    
    return base64_encoded

if __name__=='__main__':
    iamege_path = "path_to_your_image"
    text = 'prompt'

    image_format = 'jpg'
    input_data = []
    
    image64 = encode_image_to_base64(image_path)
    input_data.append({'image': f"data:image/{image_format};base64,{image64}"})

    input_data.append({'text': text})

    datas = json.dumps({
        'input': {
            'contents': input_data
        }
    })
    r = requests.post(hosts, data=datas, headers=head)
    data = json.loads(r.content.decode('utf-8'))

    if data['status_code']==200:
        if len(data['message'])!=0:
            print('Part failed for the following reasons.')
            print(data['message'])

        for result_item in data['output']['embeddings']:
            print('The following succeed.')
            print('index', result_item['index'])
            print('type', result_item['type'])
            print('embedding', len(result_item['embedding']))
    else:
        print('Processed fail')
        print(data['message'])

輸出樣本:

{
    "status_code": 200,
    "message": "",
    "output": {
        "embeddings": [
            {
                "index": 0,
                "embedding": [
                    -0.020782470703125,
                    -0.01399993896484375,
                    -0.0229949951171875,
                    ...
                ],
                "type": "text"
            }
        ]
    }
}

4.5 模型評測

在我們的樣本資料上的評測效果如下(所使用的評測檔案):

原始模型Precision

微調1個epoch的模型Precision

gme2b

Precision@1 0.3542

Precision@5 0.5280

Precision@10 0.5923

Precision@50 0.5800

Precision@100 0.5792

Precision@1 0.4271

Precision@5 0.6480

Precision@10 0.7308

Precision@50 0.7331

Precision@100 0.7404

gme7b

Precision@1 0.3958

Precision@5 0.5920

Precision@10 0.6667

Precision@50 0.6517

Precision@100 0.6415

Precision@1 0.4375

Precision@5 0.6680

Precision@10 0.7590

Precision@50 0.7683

Precision@100 0.7723

模型評測樣本指令碼

import base64
import json
import os
import requests
import numpy as np
import torch
from tqdm import tqdm
from collections import defaultdict


# Constants
ENCODING = 'utf-8'
HOST_URL = 'http://1xxxxxxxx4.cn-xxx.pai-eas.aliyuncs.com/api/xxx'
AUTH_HEADER = {'Authorization': 'ZTg*********Mw=='}

def encode_image_to_base64(image_path):
    """將影像檔編碼為 Base64 字串"""
    with open(image_path, "rb") as image_file:
        image_data = image_file.read()
        base64_encoded = base64.b64encode(image_data).decode(ENCODING)
    return base64_encoded


def load_image_features(feature_file):
    print("Begin to load image features...")
    image_ids, image_feats = [], []
    with open(feature_file, "r") as fin:
        for line in tqdm(fin):
            obj = json.loads(line.strip())
            image_ids.append(obj['image_id'])
            image_feats.append(obj['feature'])
    image_feats_array = np.array(image_feats, dtype=np.float32)
    print("Finished loading image features.")
    return image_ids, image_feats_array


def precision_at_k(predictions, gts, k):
    """
    計算前K個結果的精確率。
    
    :param predictions: [(image_id, similarity_score), ...]
    :param gts: set of ground truth image_ids
    :param k: int, 前K個結果
    :return: float, 精確率
    """
    if len(predictions) > k:
        predictions = predictions[:k]
    
    predicted_ids = {p[0] for p in predictions}
    relevant_and_retrieved = predicted_ids.intersection(gts)
    precision = len(relevant_and_retrieved) / k
    return precision


def main():
    root_dir = '/mnt/data/retrieval/data/'
    data_dir = os.path.join(root_dir, 'images')
    tag_file = os.path.join(root_dir, 'meta/test.jsonl')
    model_type = 'finetune_gme7b_final'
    save_feature_file = os.path.join(root_dir, 'features', f'features_{model_type}_eas.jsonl')
    final_result_log = os.path.join(root_dir, 'results', f'retrieval_{model_type}_log_eas.txt')
    final_result = os.path.join(root_dir, 'results', f'retrieval_{model_type}_log_eas.jsonl')

    os.makedirs(os.path.join(root_dir, 'features'), exist_ok=True)
    os.makedirs(os.path.join(root_dir, 'results'), exist_ok=True)

    tag_dict = defaultdict(list)
    gt_image_ids = []
    with open(tag_file, 'r') as f:
        lines = f.readlines()
        for line in lines:
            data = json.loads(line.strip())
            gt_image_ids.append(data['image_id'])
            img_id = data['image_id'].split('.')[0]
            for caption in data['tags']:
                tag_dict[caption.strip()].append(img_id)

    print('Total tags:', len(tag_dict.keys()))

    prefix = ''
    texts = [prefix + text for text in tag_dict.keys()]
    images = [os.path.join(data_dir, i+'.jpg') for i in gt_image_ids]
    print('Total images:', len(images))

    encode_images = True
    if encode_images:
        with open(save_feature_file, "w") as fout:
            for image_path in tqdm(images):
                image_id = os.path.basename(image_path).split('.')[0]
                image64 = encode_image_to_base64(image_path)
                input_data = [{'image': f"data:image/jpg;base64,{image64}"}]

                datas = json.dumps({'input': {'contents': input_data}})
                r = requests.post(HOST_URL, data=datas, headers=AUTH_HEADER)

                data = json.loads(r.content.decode(ENCODING))
                if data['status_code'] == 200:
                    if len(data['message']) != 0:
                        print('Part failed:', data['message'])
                    for result_item in data['output']['embeddings']:
                        fout.write(json.dumps({"image_id": image_id, "feature": result_item['embedding']}) + "\n")
                else:
                    print('Processed fail:', data['message'])

    image_ids, image_feats_array = load_image_features(save_feature_file)

    top_k_list = [1, 5, 10, 50, 100]
    top_k_list_precision  = [[] for _ in top_k_list]

    with open(final_result, 'w') as f_w, open(final_result_log, 'w') as f:
        for tag in tqdm(texts):
            datas = json.dumps({'input': {'contents': [{'text': tag}]}})
            r = requests.post(HOST_URL, data=datas, headers=AUTH_HEADER)
            data = json.loads(r.content.decode(ENCODING))

            if data['status_code'] == 200:
                if len(data['message']) != 0:
                    print('Part failed:', data['message'])

                for result_item in data['output']['embeddings']:
                    text_feat_tensor = result_item['embedding']
                    idx = 0
                    score_tuples = []
                    batch_size = 128
                    while idx < len(image_ids):
                        img_feats_tensor = torch.from_numpy(image_feats_array[idx:min(idx + batch_size, len(image_ids))]).cuda()
                        batch_scores = torch.from_numpy(np.array(text_feat_tensor)).cuda().float() @ img_feats_tensor.t()
                        for image_id, score in zip(image_ids[idx:min(idx + batch_size, len(image_ids))], batch_scores.squeeze(0).tolist()):
                            score_tuples.append((image_id, score))
                        idx += batch_size
                    
                    predictions = sorted(score_tuples, key=lambda x: x[1], reverse=True)
            else:
                print('Processed fail:', data['message'])

            gts = tag_dict[tag.replace(prefix, '')]

            # Write result
            predictions_tmp = predictions[:10]
            result_dict = {'tag': tag, 'gts': gts, 'preds': [pred[0] for pred in predictions_tmp]}
            f_w.write(json.dumps(result_dict, ensure_ascii=False, indent=4) + '\n')

            for top_k_id, k in enumerate(top_k_list):
                need_exit = False

                if k > len(gts):
                    k = len(gts)
                    need_exit = True

                prec = precision_at_k(predictions, gts, k)

                f.write(f'Tag {tag}, Len(GT) {len(gts)}, Precision@{k} {prec:.4f} \n')
                f.flush()

                if need_exit:
                    break
                else:
                    top_k_list_precision[top_k_id].append(prec)
                    
    for idx, k in enumerate(top_k_list):
        print(f'Precision@{k} {np.mean(top_k_list_precision[idx]):.4f}')


if __name__ == "__main__":
    main()

4.6 模型使用

微調後的Embedding模型,部署在EAS成功後,可以通過3.3.2中的步驟建立模型串連,用於後續的多模態資料管理中使用。