一种全新的点击率建模方案
本文作者:branxu,騰訊 CDG 應用研究員
2018 年和 2019 年騰訊算法廣告大賽都可以看做推薦系統(tǒng)問題。這類問題最重要的特征是點擊率,最大的難點是冷啟動。文本結(jié)合 2018 年比賽亞軍方案和 2019 年比賽冠軍方案中的一部分技巧,提出了一種新的點擊率建模方案,試圖解決一部分冷啟動問題。該方案復雜度很低,實現(xiàn)簡單,效果好。
問題介紹
推薦系統(tǒng)和廣告算法中,對于新用戶或者新內(nèi)容,記錄很少,如果我們直接將歷史點擊率作為特征,會存在問題。比如
1,新用戶 A 有 2 條瀏覽記錄,1 次點擊,轉(zhuǎn)化率 50%,
2,老用戶 B 有 2 條瀏覽記錄,0 次點擊,轉(zhuǎn)化率 0%。
A 和 B,只因為 1 次點擊,點擊率就相差 50%,這不合理。顯然,問題出現(xiàn)在 A,B 用戶都是新用戶,他們的歷史數(shù)據(jù)太少了,歷史點擊率自然不準。
這就像我告訴同事小明:我王者榮耀賊溜,后羿 100%勝率。實際上,我只打了兩盤后羿,其中一盤還是新手教學。同事小明可能會多嘴問一句:你打了幾盤后羿啊?但是模型不會,沒有專門調(diào)整過的模型只會默默接受我后羿 100%勝率的設(shè)定,然后給我匹配一堆王者選手。這就是冷啟動問題。
解決思路
已經(jīng) 9102 年了,我們?nèi)祟愋乔蛏系娜斯ぶ悄苣P偷挠嬎隳芰€可以,但還是太“老實”,太“傻”。所以,解決上述問題的方法就是:直接把預測結(jié)果告訴模型,別讓模型自己去算,去猜。這顯然是句廢話,不過翻譯成學術(shù)語言就不是了:給模型輸入概率,而不是頻率。
所以最好的辦法是,利用用戶的歷史點擊率,去計算用戶之后點擊的概率,再將這個概率輸入模型。通過用戶 A 的歷史點擊頻率去計算用戶 A 之后點擊的概率,聽起來不錯,但又不太可行,因為這里的信息太少了。好在我們有所有數(shù)據(jù),用所有用戶的歷史點擊頻率去預測用戶 A 之后點擊的概率,似乎有點希望。
貝葉斯平滑
新用戶 A 只有兩條瀏覽記錄,模型還不夠認識用戶 A,如何辦?如果 A 用戶能多幾次瀏覽記錄就好了。可是去哪里找那不存在的瀏覽記錄呢?我們可以假定用戶 A 和其他所有用戶是差不多,用其他用戶的歷史數(shù)據(jù)“構(gòu)造”一些瀏覽記錄,作為新用戶 A 的瀏覽記錄。這里“構(gòu)造”出來的記錄,可以理解成先驗知識。當我們見過了很多用戶之后,即使不認識新用戶 A,也會對 A 有個大概的“預期值”。貝葉斯平滑就是這樣工作的,它通過“觀測”所有用戶數(shù)據(jù),為新用戶確定一個初始預期值,這個預期值就是“先驗”。而用戶 A 自己真實的行為所產(chǎn)生的“預期值”,被稱之為后驗。最后我們將先驗和后驗綜合起來,計算一個貝葉斯平滑修正過的點擊率。
貝葉斯平滑的推導比較繁雜,也不是本文的重點,有興趣的話,可以查看:
https://www.jianshu.com/p/c98f3bb48b97
有了貝葉斯平滑,我們可以對點擊率進行修正,讓歷史轉(zhuǎn)化率這個頻率值,更加接近用戶真實點擊的概率。
連續(xù)值與深度學習
通過上文,我們可以得到一個貝葉斯平滑后的點擊率,那么直接把點擊率特征輸入深度神經(jīng)網(wǎng)絡,問題不就解決了嗎?
只能說,對于大多數(shù)普通特征也許可以這樣,但是轉(zhuǎn)化率這種強特征,這樣做太浪費了。原因如下:
近年來,推薦系統(tǒng)相關(guān)的深度網(wǎng)絡模型層出不窮:
DeepFM,Wide & Deep,Deep & Cross Network,Attentional Factorization Machine,xDeepFM,Deep Interest Network,AutoInt…
這些模型都有個共同特點:擁有 FM 層或者 Attention 層(Wide & Deep 除外)。FM 層和 Attention 層都能有效進行特征交叉,從而提高了模型精度。FM 層和 Attention 層的輸入都是向量,所以這些模型基本都需要讓特征先進入嵌入層,變成一個向量,再參與后面的特征自動交叉。
這時候,連續(xù)值特征就會很尷尬,他們無法像離散值特征一樣進入嵌入層,從而無法參與后面的特征交叉,效果大打折扣。
目前將連續(xù)值轉(zhuǎn)化成向量的解決方案主要有以下幾種:
第一種方式是對連續(xù)值特征做離散化分桶,之后將分桶后的離散值輸入到嵌入層到的嵌入向量。分桶本質(zhì)上就是做四舍五入近似,等距分桶是直接四舍五入,等頻分桶是排序后對序做四舍五入,這兩種方法會影響精度。因為近似必然會損失信息。
第二種方式也是離散化,不過是有監(jiān)督的離散化。它借鑒了決策樹的思路,枚舉所有分割點,找到一組分割點,使分割后的數(shù)據(jù)組的信息熵增益最大。這里有個比較 trick 的做法:直接用一部分數(shù)據(jù)訓練一個 lightGBM 模型,然后解析模型文件,里面記錄了 lightGBM 模型選出來的最優(yōu)分割點,直接可以用。需要注意的是,有監(jiān)督的離散化用到了數(shù)據(jù)的標簽,所以可能會帶來數(shù)據(jù)穿越。為了避免這個問題,建議訓練 lightGBM 模型的數(shù)據(jù)和深度學習的數(shù)據(jù)不要重合。
第三種方法來自 AutoInt 論文,非常有趣。它先用前面兩種方法對連續(xù)值特征 Z 做離散化,得到 Z',之后將 Z'輸入嵌入層得到嵌入向量 emd(Z'),最后用嵌入層的輸出 emd(Z')再乘以 Z。想法很巧妙:既然離散化后的特征會損失精度,那么就將原始特征再一次輸入模型。
最后一種方法來自 2019 年比賽冠軍成員郭大,下文重點介紹。
鍵值儲存網(wǎng)絡
該方法靈感來自 NLP 頂會 ACL2016 的論文《Key-Value Memory Networks for Directly Reading Documents》。
文章為深度網(wǎng)絡引入了記憶模塊,原本是用來解決 QA 問答問題的,不過簡單改進后,可以用來將構(gòu)造續(xù)值特征的專屬嵌入層。(目前推薦系統(tǒng)很多好的 idea,都來自 NLP 和 CV 領(lǐng)域。所以,學習推薦系統(tǒng)或者廣告算法,了解 NLP 和 CV 領(lǐng)域的前沿研究成果也很重要)
不多說,鍵值儲存網(wǎng)絡(Key-Value Memory Networks)結(jié)構(gòu)如下:
圖一:Key-Value Memory Networks鍵值儲存網(wǎng)絡與普通網(wǎng)絡最大的區(qū)別是可以方便的引入先驗知識,即圖中的 Knowledge Source 模塊。該模塊相當于一個內(nèi)嵌在神經(jīng)網(wǎng)絡中的“搜索引擎”,對于輸入的任何一個 Question,先在 Knowledge Source 中做一次搜索,然后將搜索結(jié)果也作為神經(jīng)網(wǎng)絡的輸入。為什么說它是內(nèi)嵌在神經(jīng)網(wǎng)絡中呢?因為搜索結(jié)果與 Question 之間存在一個相似度,這個相似度的計算是依賴神經(jīng)網(wǎng)絡的,它可以享受梯度下降帶來的優(yōu)化。
模型主要分為三個部分:Key hashing,Key addressing 和 Value reading。
Key hashing
圖二:Key hashingKey hashing 是離線計算好的。它的輸入是 Question 和 Knowlege
Source。Question 是 QA 問答問題中的提問,比如“如何打開企業(yè)微信”。Knowlege
Source 是一個類似維基百科的數(shù)據(jù)庫,里面記錄了各種詞匯,實體和知識。
Key
hashing 就是把所有 Question 里面的常用詞(出現(xiàn)次數(shù)大于某個閾值)挑出來,然后給這些詞一個編號,組成一個字典。字典的 key 是這些常用詞,value 是常用詞編號。
Key addressing
圖三:Key addressingKey addressing 就是去 Knowlege Source 里面尋找 Question 中的詞匯和短語。比如找到了:"企業(yè)","微信","企業(yè)微信","如何"等詞。Question“如何打開企業(yè)微信”會有一個訓練出的 Question embedding 值,同時,"企業(yè)","微信","企業(yè)微信","如何"等詞也都有各自的 embedding 值,被稱為 Key embeddings。用 Question embedding 分別乘以每一個 Key embeddings,再經(jīng)過一次 Softmax,就可以得到 Question 與各個 Key 的相似度權(quán)重 P。具體公式如下:
Key hashing 和 Key addressing 用上述模型解決了一個問題:Question 與 Knowlege Source 中相近詞匯的相關(guān)性。比如對于 Question"如何打開企業(yè)微信",可以得到一個相似度權(quán)重 P 字典{"企業(yè)":0.4,"微信":0.2,"企業(yè)微信":0.3,"如何":0.1}。
Value reading
圖四:value readingvalue reading 是鍵值儲存網(wǎng)絡的核心部分。還記得我們上文有個 key embedding,對應的,Key-Value Memories 還有個 value embedding,它的輸入是 Knowlege Source 里面每個詞的 id。對 value embedding 以上文的 p 為權(quán)重加權(quán)求和,便得到我們需要的向量 o。
優(yōu)勢
和傳統(tǒng)的深度神經(jīng)網(wǎng)絡比,鍵值儲存網(wǎng)絡可以方便的讓先驗知識以鍵值對的方式輸入模型(圖中的 Key-Value Memories)。這意味著,神經(jīng)網(wǎng)絡的輸入值可以直接是多個鍵值對組成的字典。
舉個例子,傳統(tǒng)神經(jīng)網(wǎng)絡只能將數(shù)字作為特征,比如:身高(173),體重(90)或者年齡(25)。而鍵值儲存網(wǎng)絡可以將興趣 ({‘籃球’:0.5,'足球':0.2,'臺球':0.1}) 作為特征,直接輸入給模型。字典特征的 key 是實體,使用 LabelEncoding 后可以進入嵌入層,value 是其權(quán)重。鍵值儲存網(wǎng)絡可以方便的將輸入的字典特征轉(zhuǎn)化成上文的向量 o。
連續(xù)值鍵值儲存網(wǎng)絡
回到最開始的問題,我們想找到一個將連續(xù)值轉(zhuǎn)成向量的方法,但上文卻一直在講鍵值儲存網(wǎng)絡,為什么?
因為鍵值儲存網(wǎng)絡實現(xiàn)了字典特征->向量的轉(zhuǎn)換,我們希望的是連續(xù)值->向量的轉(zhuǎn)換。所以,借助鍵值儲存網(wǎng)絡,只需要再實現(xiàn)連續(xù)值->字典特征的轉(zhuǎn)化就大功告成了。連續(xù)值->向量很難,但是連續(xù)值->字典特征方式很多,易于實現(xiàn)。
假定有了連續(xù)值->字典特征的轉(zhuǎn)化,那么總體架構(gòu)和鍵值記憶網(wǎng)絡基本一致,如下圖所示:
圖五:連續(xù)值鍵值儲存網(wǎng)絡連續(xù)值->字典特征的轉(zhuǎn)化即圖中的 Key-Value Memory,如何實現(xiàn)這部分應當結(jié)合具體的業(yè)務場景,數(shù)據(jù)分布。這里先介紹下郭大的做法吧:
將連續(xù)值特征縮放至[0,1]區(qū)間
在[0,1]區(qū)間找 n 等分點,比如 n=6 時,就是(0, 0.2, 0.4, 0.6, 0.8, 1)
依次計算連續(xù)值 x 與 n 等分點的距離,比如 x=0.3,n=6,就是(0.3, 0.1, 0.1, 0.3, 0.5,0.7),之后構(gòu)造字典特征{0:0.3, 1:0.1, 2:0.1, 3:0.3, 4:0.5, 5:0.7}
對字典特征的 value 取倒數(shù)后 softmax,具體相似度公式如下:
python 偽代碼: {i: softmax(1/(q-i/n+1e-15)) for i in range(n+1)},其中 q 為浮點數(shù)特征,n 為等分點個數(shù)。這里加上 1e-15 是為了防止 q 正好等于某個等分點時,分母為 0。
郭大的方法將字典特征的 key 定義為[0,1]區(qū)間的等分點,之后對浮點數(shù)與各等分點的距離做取倒和 softmax 變換。取倒是為了保證浮點數(shù)越接近等分點,權(quán)重越大。softmax 變換是為了保證所有權(quán)重之和為 1。
實踐中發(fā)現(xiàn),當 q 與某個等分點較接近時,value 中除該等分點對應的值外,都非常接近 0。這主要是因為 softmax 函數(shù)會指數(shù)級加大距離間差異。
為了緩解這種情況,我在最近的代碼里使用如下相似度公式:
該公式取距離平方反比為權(quán)值,之后將權(quán)值縮放至總和為 1。用該公式得到的權(quán)值比較"分散",可以讓模型更好的學習那些冷門分位數(shù)的嵌入表示。
概率分布特征
截至目前,文章講了點擊率特征的貝葉斯平滑,以及如何在不損失精度的情況下把浮點數(shù)特征(比如點擊率特征)輸入神經(jīng)網(wǎng)絡。
如果把點擊率看成一個普通浮點數(shù),問題已經(jīng)解決。但是點擊率并不普通,點擊率可以被認為是用戶是否點擊廣告這個隨機變量的期望值。
用戶是否點擊廣告實際上是一個隨機變量,點擊率就是用這個隨機變量的期望值作為特征,去描述它。這樣做實際上是用一個值去代表一個復雜的分布,必然會帶來信息損失。舉個例子,A 用戶瀏覽 20 次,點擊 10 次。B 用戶瀏覽 100 次,點擊 50 次。A 和 B 的點擊率都是 50%,但是他們是否點擊廣告的概率分布卻大不一樣:
圖六:用戶A和用戶B否點擊廣告的概率分布雖然 AB 兩用戶點擊率都是 50%,但是 B 用戶點擊次數(shù)更多,所以 B 用戶的點擊率更置信,B 用戶的概率分布也更集中。這就體現(xiàn)出點擊率特征的弊端,它只能描述概率分布的期望,無法完整描述概率分布。
我們希望完整描述概率分布給模型,我們希望精確區(qū)分出點擊率很相似但總瀏覽數(shù)差異很大的那群人。這個問題可以被定義為如何向模型描述一個概率分布。
用戶是否點擊廣告的概率分布是連續(xù)的,用概率密度函數(shù)可以表示??梢詫Ω怕拭芏群瘮?shù)函數(shù)進行分段近似,分別統(tǒng)計它在[0,0.1),[0.1,0.2),[0.2,0.3),[0.3,0.4)…區(qū)間的平均值,用這些平均值來表示這個分布。形式如下:
{[0,0.1):0.1,[0.1,0.2):0.2,[0.2,0.3):0.4,[0.3,0.4):0.4,…}
該形式其實也是字典特征,它的 key 是區(qū)間,value 是點擊率這個隨機變量落在各區(qū)間的概率。如此一來,可以直接將這個字典特征輸入鍵值儲存網(wǎng)絡。這種方式利用隨機變量的概率分布,跳過了連續(xù)值->字典特征這一步,直接做隨機變量->字典特征,避免了上文中的人工設(shè)計相似度公式。
如果構(gòu)造的特征可以被看做是隨機變量,那么就可以利用數(shù)學工具得到他的概率分布,概率分布分段近似得到字典特征,最后將字典特征輸入鍵值儲存網(wǎng)絡。
代碼實現(xiàn)與復雜度分析
上文的方法在代碼實現(xiàn)上很容易,用途廣泛(任何使用了嵌入層的網(wǎng)絡都可以用)。
代碼主要有四部分:貝葉斯平滑,隨機變量->字典特征的轉(zhuǎn)換,浮點數(shù)>字典特征的轉(zhuǎn)換和鍵值儲存網(wǎng)絡。
相關(guān)第三方庫
import?numpy?as?np import?random import?pandas?as?pd import?scipy.special?as?special from?sklearn.model_selection?import?StratifiedKFold,KFold from?scipy?import?stats from?collections?import?OrderedDict,?namedtuple from?itertools?import?chain from?tensorflow.python.keras.initializers?import?RandomNormal from?tensorflow.python.keras.layers?import??Embedding,?Input,?Flatten from?tensorflow.python.keras.regularizers?import?l2貝葉斯平滑
class?BayesianSmoothing(object):def?__init__(self,?alpha,?beta):self.alpha?=?alphaself.beta?=?betadef?sample(self,?alpha,?beta,?num,?imp_upperbound):sample?=?np.random.beta(alpha,?beta,?num)I?=?[]C?=?[]for?clk_rt?in?sample:imp?=?random.random()?*?imp_upperboundclk?=?imp?*?clk_rtI.append(int(imp))C.append(int(clk))return?I,?Cdef?update(self,?imps,?clks,?iter_num,?epsilon):for?i?in?range(iter_num):new_alpha,?new_beta?=?self.__fixed_point_iteration(imps,?clks,?self.alpha,?self.beta)if?abs(new_alpha-self.alpha)<epsilon?and?abs(new_beta-self.beta)<epsilon:breakself.alpha?=?new_alphaself.beta?=?new_betadef?__fixed_point_iteration(self,?imps,?clks,?alpha,?beta):numerator_alpha?=?0.0numerator_beta?=?0.0denominator?=?0.0for?i?in?range(len(imps)):numerator_alpha?+=?(special.digamma(clks[i]+alpha)?-?special.digamma(alpha))numerator_beta?+=?(special.digamma(imps[i]-clks[i]+beta)?-?special.digamma(beta))denominator?+=?(special.digamma(imps[i]+alpha+beta)?-?special.digamma(alpha+beta))return?alpha*(numerator_alpha/denominator),?beta*(numerator_beta/denominator)隨機變量(beta 分布)->字典特征
def?beta_ppf(alpha,?beta,?dim):return?stats.beta(alpha,?beta).ppf([x/(dim+1)?for?x?in?range(0,dim+2)])def?beta_prior_feat_2_vec(data,?key_col,?count_col,?sum_col,?dim):data_simple?=?data.drop_duplicates([key_col],keep='last')bs?=?BayesianSmoothing(1,?1)bs.update(data_simple[count_col].values,?data_simple[sum_col].values,?1000,?0.0000000001)if?np.isnan(bs.alpha)?or?np.isnan(bs.beta):bs.alpha,?bs.beta?=?0,?0data[key_col+'_beta_cdf_value']?=?list(map(lambda?x,y:beta_cdf(x,y,dim),?data[sum_col]+bs.alpha,?data[count_col]-data[sum_col]+bs.beta))data[key_col?+?'_beta_ppf_value']?=?list(map(lambda?x,y:beta_ppf(x,y,dim),?data[sum_col]?+?bs.alpha,?data[count_col]?-?data[sum_col]?+?bs.beta))data[key_col?+?'_beta_key']?=?[np.array([i?for?i?in?range(dim)])?for?_?in?range(data.shape[0])]return?data[key_col+'_beta_cdf_value'].values,?data[key_col?+?'_beta_ppf_value'].values,?data[key_col?+?'_beta_key'].values浮點數(shù)->字典特征
def?numpy_softmax(x):orig_shape?=?x.shapeif?len(x.shape)?>?1:exp_minmax?=?lambda?x:?np.exp(x?-?np.max(x))denom?=?lambda?x:?1.0?/?np.sum(x)x?=?np.apply_along_axis(exp_minmax,1,x)denominator?=?np.apply_along_axis(denom,1,x)if?len(denominator.shape)?==?1:denominator?=?denominator.reshape((denominator.shape[0],1))x?=?x?*?denominatorelse:x_max?=?np.max(x)x?=?x?-?x_maxnumerator?=?np.exp(x)denominator?=??1.0?/?np.sum(numerator)x?=?numerator.dot(denominator)assert?x.shape?==?orig_shapereturn?xdef?float2vec(float_feat,?bar_num?=?20,?method?=?'gravitation'):float_feat?=?(float_feat-np.min(float_feat))*1.0?/?np.max(float_feat-np.min(float_feat))key_array?=?np.array([[i*1.0/(bar_num?+?1)?for?i?in?range(bar_num?+?1)]]?*?len(float_feat))value_array?=?Noneif?method?==?'gravitation':value_array?=?1/(np.abs(key_array?-?float_feat[:,None]?+?0.00001))**2value_array?=?value_array/np.sum(value_array,axis=1,?keepdims=True)if?method?==?'sofmax':value_array?=?1?/?np.abs(key_array?-?float_feat[:,?None]?+?0.00001)value_array?=?numpy_softmax(value_array)return?key_array,value_array網(wǎng)絡結(jié)構(gòu)
def?get_varlen_multiply_list(embedding_dict,?features,?varlen_sparse_feature_columns_name_dict):multiply_vec_list?=?[]print(embedding_dict)for?key_feature?in?varlen_sparse_feature_columns_name_dict:for?value_feature?in?varlen_sparse_feature_columns_name_dict[key_feature]:key_feature_length_name?=?key_feature.name?+?'_seq_length'if?isinstance(value_feature,?VarLenSparseFeat):value_input?=?embedding_dict[value_feature.name]elif?isinstance(value_feature,?DenseFeat):value_input?=?features[value_feature.name]else:raise?TypeError("Invalid?feature?column?type,got",type(value_feature))if?key_feature_length_name?in?features:varlen_vec?=?SequenceMultiplyLayer(supports_masking=False)([embedding_dict[key_feature.name],?features[key_feature_length_name],?value_input])vec?=?SequencePoolingLayer('sum',?supports_masking=False)([varlen_vec,?features[key_feature_length_name]])else:varlen_vec?=?SequenceMultiplyLayer(supports_masking=True)([embedding_dict[key_feature.name],?value_input])vec?=?SequencePoolingLayer('sum',?supports_masking=True)(?varlen_vec)multiply_vec_list.append(vec)return?multiply_vec_listclass?SequenceMultiplyLayer(Layer):def?__init__(self,?supports_masking,?**kwargs):super(SequenceMultiplyLayer,?self).__init__(**kwargs)self.supports_masking?=?supports_maskingdef?build(self,?input_shape):if?not?self.supports_masking:self.seq_len_max?=?int(input_shape[0][1])super(SequenceMultiplyLayer,?self).build(input_shape)??#?Be?sure?to?call?this?somewhere!def?call(self,?input_list,?mask=None,?**kwargs):if?self.supports_masking:if?mask?is?None:raise?ValueError("When?supports_masking=True,input?must?support?masking")key_input,?value_input?=?input_listmask?=?tf.cast(mask[0],?tf.float32)mask?=?tf.expand_dims(mask,?axis=2)else:key_input,?key_length_input,?value_input?=?input_listmask?=?tf.sequence_mask(key_length_input,self.seq_len_max,?dtype=tf.float32)mask?=?tf.transpose(mask,?(0,?2,?1))embedding_size?=?key_input.shape[-1]mask?=?tf.tile(mask,?[1,?1,?embedding_size])key_input?*=?maskif?len(tf.shape(value_input))?==?2:value_input?=?tf.expand_dims(value_input,?axis=2)value_input?=?tf.tile(value_input,?[1,?1,?embedding_size])return?tf.multiply(key_input,value_input)def?compute_output_shape(self,?input_shape):return?input_shape[0]def?compute_mask(self,?inputs,?mask):if?self.supports_masking:return?mask[0]else:return?Nonedef?get_config(self,?):config?=?{'supports_masking':?self.supports_masking}base_config?=?super(SequenceMultiplyLayer,?self).get_config()return?dict(list(base_config.items())?+?list(config.items()))上述代碼除貝葉斯平滑轉(zhuǎn)載自網(wǎng)絡外,均為原創(chuàng)。
改進后的鍵值網(wǎng)絡與連續(xù)值離散化后接入嵌入層的方法相比,沒有增加訓練參數(shù),只是多做了一次向量加權(quán)求和,多增加了一些權(quán)重的輸入。另一方面,改進后的鍵值網(wǎng)絡中,分位數(shù)或者概率區(qū)間個數(shù)是可以人工調(diào)整的,當分位數(shù)或者概率區(qū)間個數(shù)為 1 時,該方法就退化成離散化后接入嵌入層。
總結(jié)
以上是生活随笔為你收集整理的一种全新的点击率建模方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 内容 AI:建立统一的跨媒体多模态内容理
- 下一篇: 一文读懂 HTTP/1HTTP/2HTT