自己動(dòng)手寫一個(gè)推薦系統(tǒng)
本文關(guān)鍵詞:推薦系統(tǒng),由筆耕文化傳播整理發(fā)布。
自己動(dòng)手寫一個(gè)推薦系統(tǒng)
廢話:
最近朋友在學(xué)習(xí)推薦系統(tǒng)相關(guān),說是實(shí)現(xiàn)完整的推薦系統(tǒng),于是我們?nèi)恢粫?huì)有一些討論和推導(dǎo),想想索性整理出來。
在文中主要以工程中做推薦系統(tǒng)的流程著手,穿插一些經(jīng)驗(yàn)之談,并對(duì)于推薦系統(tǒng)的算法的學(xué)術(shù)界最新的研究進(jìn)展和流派作一些介紹。當(dāng)然由于我做推薦系統(tǒng)之時(shí)還年幼,可能有很多偏頗甚至錯(cuò)誤的見解,就當(dāng)拋磚引玉,還請(qǐng)各位大大指點(diǎn)。
Reading lists
雖然很多人覺得作為AI的分支之一,推薦跟自然語言處理等問題的難度不可同日而語。但所謂磨刀不誤砍柴工,我覺得,至少在開工前先應(yīng)該閱讀這樣基本書,起碼要看看目錄,以對(duì)于推薦系統(tǒng)有個(gè)初步的了解。
中文書籍:
1.《推薦系統(tǒng)實(shí)踐》項(xiàng)亮
這本書你說他好吧,不如recommend handbook;你說他不好吧,確實(shí)又把很多基礎(chǔ)而簡(jiǎn)單的問題講的很詳細(xì),而且還是中文的。薄薄的一本書,分分鐘可以翻完,總而言之,是一本入門的好書。
外文書籍:
1.《Recommender Systems Handbook》Paul B. Kantor
其實(shí)所有敢自稱handbook的書都是神書。這本書寫的非常細(xì),而且非常全,如果你去看一些推薦系統(tǒng)的一些比較冷門的topic的paper,比如fighting spam的,甚至能發(fā)現(xiàn)很多paper就是直接引用這本書里相關(guān)章節(jié)的內(nèi)容?梢哉f這本書是做推薦同學(xué)必備的枕邊書,沒看過這本書出去吹牛逼時(shí)你都不好意思說自己是做推薦的,不過說實(shí)在,真正看完的沒幾個(gè),一般是用到哪里查哪里,我剛才就去豆瓣驗(yàn)證了,幾個(gè)做推薦的都是在讀,一群文藝青年都是想讀。此書唯一的缺點(diǎn)是沒有出新版,有些地方已經(jīng)成為時(shí)代的眼淚了。
2.《Recommender Systems - An Introduction》 Dietmar Jannach
跟上面一本差不多,學(xué)院派的綜述型的書,厚厚一本,不看的時(shí)候當(dāng)枕頭用。
3.《mahout in action》Sean Owen
上面的一中一外的書都是理論基礎(chǔ)的書,這本書就和工程有些沾邊。如果你要用mahout框架來做推薦系統(tǒng),那么是必須要掃一遍的;如果你不用mahout,看看其中的流程和代碼組織也是有一定好處的。
Paper:
由于《Recommender Systems Handbook》很多地方已經(jīng)成為了時(shí)代的眼淚,這里推薦一些綜述,以便開闊眼界。
一篇是physics report上面的recommend system這篇綜述,可以說是最新最全的綜述了,看完后學(xué)術(shù)界都在折騰啥分分鐘就清楚了。
比較經(jīng)典的是recommend system的state of art這篇綜述,很多老一輩的同志喜歡推薦的,說實(shí)在這篇因?yàn)槟甏眠h(yuǎn),也已經(jīng)成為時(shí)代的眼淚了。
開工:
上面給了一些reading lists,并不是說要讀完所有的才能開工,只是說,先看完個(gè)目錄,哪里不懂查哪里。在下面介紹的做推薦系統(tǒng)的流程中,我只是想給大家介紹個(gè)普通的推薦系統(tǒng)該怎么做,所以很多地方都有偷懶,還請(qǐng)大家見諒。而且由于我不是做的在線的推薦系統(tǒng),而是屬于隔天推薦的那種離線的,所以敘述工程的時(shí)候就是只敘述離線的推薦。
數(shù)據(jù)準(zhǔn)備:
一般來說,做推薦系統(tǒng)的數(shù)據(jù)一般分兩種,一種從在線的讀取,比如用戶產(chǎn)生一個(gè)行為,推薦系統(tǒng)就反應(yīng)下(傳說豆瓣fm就是這么做的?),還有一種就是從數(shù)據(jù)庫里讀。
我個(gè)人一般是這樣做的:跟做反作弊的人打個(gè)招呼,讓他們?cè)谟浻脩粜袨閿?shù)據(jù)的時(shí)候順便存到各個(gè)線上服務(wù)器上,再寫個(gè)php腳本,從各個(gè)服務(wù)器上把我需要的日志抓過來,然后當(dāng)日要的最新的數(shù)據(jù)就來了。
但是這種地方其實(shí)存儲(chǔ)的數(shù)據(jù)是加了一些判斷的,也就是說是分類記錄的(因?yàn)楹芏嘤涗浭莿e人刷的,比如丟一個(gè)鏈接到qq群里讓別人幫忙投票什么的),這里不細(xì)說,到后面fighting spam的地方再討論。
數(shù)據(jù)過濾:
當(dāng)我們得到了每天產(chǎn)生的數(shù)據(jù)后,說實(shí)在這些數(shù)據(jù)實(shí)在是太多了,我們當(dāng)然用不到這么多,就要寫個(gè)過濾模塊,把一些我們用不到的數(shù)據(jù)過濾掉。
我一般是這樣做的:寫個(gè)python的腳本,把過濾器放到一個(gè)單獨(dú)的模塊,要用的過濾器就到責(zé)任鏈里面注冊(cè)下。這樣別人和自己維護(hù)起來也方便點(diǎn),順便一說,過濾的東西一般來說有這樣幾種:一種是一個(gè)item只有一個(gè)user打過分的,而且以前沒有人打分的,這樣的數(shù)據(jù)放到推薦的模型里去跑雖然mahout會(huì)自動(dòng)無視它,但其實(shí)按照power law來說是有不少的,,內(nèi)存能省就省吧;還有一種是有些黑名單的,有item和user各自的黑名單,這些也是事先要去掉的。
數(shù)據(jù)存儲(chǔ):
這個(gè)就是大家仁者見仁,智者見智的時(shí)候了,因?yàn)橐话銇碚f,是你選用的算法和算法具體的實(shí)現(xiàn)方式以及基礎(chǔ)架構(gòu)決定了你的存儲(chǔ)方式,就不多說了。
我一般是這樣做的:一般做成增量處理的方式,然后每天一計(jì)算,一周一回滾。由于算法實(shí)現(xiàn)的特殊性,每40個(gè)item user對(duì)存儲(chǔ)在一起,有點(diǎn)類似于bitmap吧。
推薦系統(tǒng)算法部分:
這部分以前寫過類似的小記錄和心得筆記之類的東西,就直接貼了_(:з」∠)_
這里的推薦系統(tǒng)的核心算法主要用mahout實(shí)現(xiàn)。
各種算法對(duì)于推薦都有著自己的特定的假設(shè),對(duì)于什么時(shí)候什么樣的算法會(huì)有比較好的performance應(yīng)該對(duì)于假設(shè)反復(fù)驗(yàn)證。說白了就是做實(shí)驗(yàn)。
然后我們一般用什么算法呢,看看mahout給的算法吧,花樣繁多,什么item based,user based,slop-one,SVD等等,常用的都有了,那我們要用什么算法呢。
先簡(jiǎn)單說下user based的算法在mahout中的一些實(shí)現(xiàn):
第一步應(yīng)該先算出所有人的相似度矩陣W,再去對(duì)于item進(jìn)行遍歷,事實(shí)上mahout也是這樣做的。
相似度矩陣也許可以保留下來,這樣可以用來做譜聚類來驗(yàn)證。
UserSimilarity 封裝了用戶之間的相似性
UserSimilarity similarity = new PearsonCorrelationSimilarity (model);
UserNeighborhood封裝了最相似的用戶組
UserNeighborhood neighborhood = new NearestNUserNeighborhood (2, similarity, model);
總而言之,用DataModel來生成data model,用UserSimilarity生成User-user之間的相似度矩陣,用戶的鄰居的定義用UserNeighborhood定義,推薦引擎使用Recommender實(shí)現(xiàn)。
對(duì)于推薦的評(píng)判是使用evaluator來進(jìn)行評(píng)判
double score = evaluator.evaluate(recommenderBuilder, null, model, 0.95, 0.05);
用95%的數(shù)據(jù)構(gòu)建模型,用5%的數(shù)據(jù)進(jìn)行test
Fixed-size neighborhoods
對(duì)于到底用多少人作為用戶周圍的一圈,我們事實(shí)上沒有一個(gè)確定的值,就像做生物實(shí)驗(yàn)一樣需要不斷在特定的數(shù)據(jù)集里跑出來。
Threshold-based neighborhood
當(dāng)然,我們也可以定義個(gè)threshold去找出最相似的用戶群。
Threshold定義為-1到1(相似度矩陣返回的相似度就是在這個(gè)范圍)
new ThresholdUserNeighborhood(0.7, similarity, model)
我們對(duì)于各個(gè)算法做個(gè)簡(jiǎn)單的比(吐)較(槽):
(假設(shè)我們是要像亞馬遜一樣對(duì)商品做推薦,然后我們最終是要做top k的推薦)
Item based
一般來說item-based要跑得快一些,因?yàn)閕tem比user少
Slop one
說實(shí)在話我覺得在對(duì)于一些個(gè)人品味比較看重的東西上這個(gè)算法不是很靠譜
但是它在GroupLens 10 million數(shù)據(jù)的結(jié)果是0.65
當(dāng)然,對(duì)于股票系統(tǒng)這種還是可取的
這個(gè)算法的假設(shè)是對(duì)于不同item的preference有種線性關(guān)系
不過slope-one有個(gè)好處是它的online算的很快,并且它的性能不由用戶的數(shù)量決定
在mahout中的調(diào)用方法是new SlopeOneRecommender(model)
這個(gè)方法提供了兩種weight:weighting based on count and on standard deviation
count 是越多的user的給越多的權(quán)重,對(duì)出的是一個(gè)加權(quán)的平均數(shù)
standard deviation則是越低的deviation給予越高的權(quán)重
這兩個(gè)weight是默認(rèn)采用的,當(dāng)然disable它們只會(huì)讓結(jié)果變得稍微壞一點(diǎn)0.67
不過這個(gè)算法有個(gè)很明顯的缺點(diǎn)是比較占內(nèi)存
但是幸運(yùn)的是我們可以把它存在數(shù)據(jù)庫里:MySQLJDBCDataModel
Singular value decomposition–based recommenders
事實(shí)上,盡管SVD失去了一些信息,但是有時(shí)候它可以改善推薦的結(jié)果。
這個(gè)過程在有用的方式平滑了輸入
new SVDRecommender(model, new ALSWRFactorizer(model, 10, 0.05, 10))
第一個(gè)參數(shù)10是我們的目標(biāo)屬性個(gè)數(shù)
第二個(gè)屬性是lambda->regularization
最后一個(gè)參數(shù)是training step跑的次數(shù)
KnnItemBasedRecommender
囧,事實(shí)上這個(gè)是用的knn的方式來做的算法,和前面的選擇一個(gè)threshold然后圈畫用戶的算法是比較像的
但是用knn代價(jià)是非常高的,因?yàn)樗ケ容^所有的items的對(duì)
ItemSimilarity similarity = new LogLikelihoodSimilarity(model);
Optimizer optimizer = new NonNegativeQuadraticOptimizer();
return new KnnItemBasedRecommender(model, similarity, optimizer, 10);
結(jié)果也還不算差,是0.76
Cluster-based recommendation
基于聚類的推薦可以說是基于用戶的推薦的算法的變體中最好的一種思路
對(duì)于每一個(gè)聚類里面的用戶進(jìn)行推薦
這個(gè)算法在推薦里面是非?斓,因?yàn)槭裁炊际孪人愫昧恕?/span>
這個(gè)算法對(duì)于冷啟動(dòng)還是挺不錯(cuò)的
感覺mahout里面用的聚類算法應(yīng)該類似于Kmeans?
TreeClusteringRecommender
UserSimilarity similarity = new LogLikelihoodSimilarity(model);
ClusterSimilarity clusterSimilarity =
new FarthestNeighborClusterSimilarity(similarity);
return new TreeClusteringRecommender(model, clusterSimilarity, 10);
注意的是兩個(gè)cluster之間的相似度是用ClusterSimilarity來定義的
其中cluster之間的相似度還有NearestNeighborClusterSimilarity可選
吐槽:
對(duì)于算法的選擇,其實(shí)我們是要和我們要推薦的目標(biāo)掛鉤的。為什么最近學(xué)術(shù)界搞SVD那一系的算法這么火,什么LDA,plsa各種算法層出不窮,事實(shí)上因?yàn)閚etflix的要求是要優(yōu)化RMSE,在機(jī)器學(xué)習(xí)的角度來看,類似于回歸問題,而工業(yè)界的角度來說,我們一般的需求是做top k的推薦,更加類似于分類問題。所以為什么相比用SVD系列的算法,用item based這種更加ad hoc的算法要表現(xiàn)的更好一些。當(dāng)然2012年的KDD cup第一名的組用的是item based+SVD的算法,這是后話。
那么我就假設(shè)解我們這個(gè)top k的商品推薦的問題就用item based好了(速度快,結(jié)果又不算差),接下來就是確定相似度了。
相似度確定:
我們對(duì)于各個(gè)相似度做一些比(吐)較(槽):
PearsonCorrelationSimilarity
Pearson correlation:
coeff = corr(X , Y);
function coeff = myPearson(X , Y)
% 本函數(shù)實(shí)現(xiàn)了皮爾遜相關(guān)系數(shù)的計(jì)算操作
%
% 輸入:
% X:輸入的數(shù)值序列
% Y:輸入的數(shù)值序列
%
% 輸出:
% coeff:兩個(gè)輸入數(shù)值序列X,Y的相關(guān)系數(shù)
%
if length(X) ~= length(Y)
error('兩個(gè)數(shù)值數(shù)列的維數(shù)不相等');
return;
end
fenzi = sum(X .* Y) - (sum(X) * sum(Y)) / length(X);
fenmu = sqrt((sum(X .^2) - sum(X)^2 / length(X)) * (sum(Y .^2) - sum(Y)^2 / length(X)));
coeff = fenzi / fenmu;
end %函數(shù)myPearson結(jié)束
當(dāng)兩個(gè)變量的標(biāo)準(zhǔn)差都不為零時(shí),相關(guān)系數(shù)才有定義,皮爾遜相關(guān)系數(shù)適用于:
(1)、兩個(gè)變量之間是線性關(guān)系,都是連續(xù)數(shù)據(jù)。
(2)、兩個(gè)變量的總體是正態(tài)分布,或接近正態(tài)的單峰分布。
(3)、兩個(gè)變量的觀測(cè)值是成對(duì)的,每對(duì)觀測(cè)值之間相互獨(dú)立。
1.沒有考慮到用戶偏好重合的items的數(shù)量
2,只有一個(gè)item是相交錯(cuò)的話是不能得到correlation的,對(duì)于比較稀疏或者小的數(shù)據(jù)集這是個(gè)要注意的問題。不過一般來說兩個(gè)用戶之間只有一個(gè)item重疊從直覺上來說也不那么相似
Pearson correlation一般出現(xiàn)在早期的推薦的論文和推薦的書里,不過并不總是好的。
在mahout中使用了一個(gè)增加的參數(shù)Weighting.WEIGHTED,適當(dāng)使用可以改善推薦結(jié)果
EuclideanDistanceSimilarity
Return 1 / (1 + d)
CosineMeasureSimilarity
當(dāng)two series of input values each have a mean of 0(centered)時(shí)和PearsonCorrelation是有相同結(jié)果的
所以在mahout中我們只要簡(jiǎn)單的使用PearsonCorrelationSimilarity就好
Spearman correlation
這個(gè)方法用rank的方式,雖然失去了具體的打分信息,不過卻保留了item的order
Return的結(jié)果是-1和1兩個(gè)值,不過和pearson一樣,對(duì)于只有一個(gè)item重疊的也算不出。
而且這個(gè)算法比較慢,因?yàn)樗愫痛鎯?chǔ)rank的信息。所以paper多而實(shí)際用的少,對(duì)于小數(shù)據(jù)集才值得考慮
CachingUserSimilarity
UserSimilarity similarity = new CachingUserSimilarity(
new SpearmanCorrelationSimilarity(model), model);
Ignoring preference values in similarity with the Tanimoto coefficient
TanimotoCoefficientSimilarity
如果一開始就有preference value,當(dāng)數(shù)據(jù)中signal比noise多的時(shí)候可以用這個(gè)方法。
不過一般來說有preference信息的結(jié)果要好。
log-likelihood
Log-likelihood try to access how unlikely 這些重疊的部分是巧合
結(jié)果的值可以解釋為他們的重合部分并不是巧合的概念
算法的結(jié)果可能比tanimoto要好,是一個(gè)更加聰明的metric
Inferring preferences
對(duì)于數(shù)據(jù)量比較小的數(shù)據(jù),pearson很難handle,比如一個(gè)user只express一個(gè)preference
于是要估計(jì)相似度么.........
AveragingPreferenceInferrer
setPreferenceInferrer().
不過這種辦法在實(shí)際中并不是有用的,只是在很早的paper中mention到
通過現(xiàn)在的信息來估計(jì)并不能增加什么東西,并且很大的降低了計(jì)算速度
最終我們要通過實(shí)驗(yàn)來比較上面的相似度,一般來說是用準(zhǔn)確率,召回率,覆蓋率評(píng)測(cè)。
這里有一篇Building Industrial-scale Real-world Recommender Systems
寫netflex的,非常好,我就不獻(xiàn)丑多說了,所以上面只是吐槽下常見的算法和相似度。
其實(shí)算法按流派分是分為下面這些類別的,大家有興趣也可以了解下,我這里就不多做介紹:
Similarity-based methods
Dimensionality Reduction Techniques
Dimensionality-based methods
Diffusion-based methods
Social fltering
Meta approaches
我上面所說的相似度和推薦算法,只能算是Similarity-based methods和Dimensionality Reduction Techniques里的非常小的一小部分,只當(dāng)拋磚引玉了。
Ps:剛在豆瓣上問了下,他們說就用前兩種,事實(shí)上我也是覺得協(xié)同過濾+SVD 偶爾做做topic model就夠了 如果沒事干再上點(diǎn)social trusted的東西
增加規(guī)則:
記得hulu在做presentation的時(shí)候說過“不能做規(guī)則定制的推薦系統(tǒng)不是一個(gè)好的推薦系統(tǒng)”(好像是這樣吧......)事實(shí)上我們做出來的推薦的結(jié)果是需要做很多處理再展現(xiàn)給用戶的,這時(shí)候就是需要增加規(guī)則的時(shí)候了。
1.改善推薦效果:有些協(xié)同過濾的算法,做出來的結(jié)果不可避免的產(chǎn)生一些讓人啼笑皆非的結(jié)果,比如用戶什么都買,導(dǎo)致你有可能跟姑娘們推薦的時(shí)候在佛祖下面推薦泳裝什么的(真實(shí)的故事)。這時(shí)候就有兩種辦法,一種就是調(diào)整模型,一種就是增加規(guī)則做一定的限制。再舉個(gè)常見的例子就是有時(shí)候會(huì)在推薦冬裝的時(shí)候出現(xiàn)夏裝什么的,這時(shí)候一般我的處理方式是給這種季節(jié)性商品做一個(gè)隨時(shí)間的衰退。
2.增加廣告和導(dǎo)向:插入廣告,我們的最愛,這個(gè)就不多說了,靠規(guī)則。而所謂用戶喜歡的,其實(shí)不一定就是最好的,比如用戶一般來說就喜歡便宜的,還有什么韓流爆款之流,如果你推出來的東西都是這樣的,整個(gè)系統(tǒng)就變成洗剪吹大集合了,非常影響定位,這時(shí)候也要上規(guī)則。
3.做一些數(shù)據(jù)挖掘和fighting spam的工作:這個(gè)在fighting spam的地方細(xì)說
可視化參數(shù)調(diào)整:
做完上面的工作,一般來說推薦系統(tǒng)的基礎(chǔ)架構(gòu)就差不多了,但是往往各個(gè)算法以及你自己上的規(guī)則都有多如牛毛的參數(shù)要調(diào)整,這時(shí)候一般要自己寫個(gè)測(cè)試腳本,將調(diào)整的結(jié)果可視化下一下,我個(gè)人推薦的是highchart,看參數(shù)以及比較各個(gè)指標(biāo)非常清爽, 還可以自己做一些比如是取log之類的定制,很是方便。
調(diào)整參數(shù)以及上線:
上線前有兩個(gè)事要做,一般來說就是離線測(cè)試和AB test。
離線測(cè)試就是把數(shù)據(jù)抽樣一部分,分為train data和test data,然后評(píng)估一些準(zhǔn)確率,召回率以及coverage之類的指標(biāo),用上面的可視化工具觀測(cè)比較下,感覺差不多了把pm叫過來讓她給小編們看看,看看審美和效果之類的。這是比較粗糙的,有的地方做的比較過細(xì),還有用戶調(diào)研和請(qǐng)一些人來實(shí)際使用,這是后話。
AB test也是大家最喜歡的地方了。因?yàn)檎f實(shí)在,評(píng)估推薦系統(tǒng)學(xué)術(shù)界是看準(zhǔn)確率那一些東西,但是工業(yè)界還是看pv uv轉(zhuǎn)化率這種實(shí)打?qū)崕硇б娴臇|西,而AB test就是評(píng)估這些的。我個(gè)人是比較推薦這種方法,說不上好,只是拋磚引玉,就是按一般的做法先空跑一個(gè)星期,然后再把系統(tǒng)上線實(shí)際做算法pk,然后選取的實(shí)驗(yàn)用戶一半的幾率進(jìn)入原來的算法的推薦,一半的幾率進(jìn)入你的算法的推薦,每天看看轉(zhuǎn)化率之間的比較,順便還可以調(diào)下參數(shù)做做實(shí)驗(yàn)。如果算法穩(wěn)定表現(xiàn)較好,就差不多了。
Fighting spam:
俗話說,有人的地方就有江湖,有推薦的地方就有人刷。刷子一般分三種類型的:average random和nuke。一般來說,average和random比較好對(duì)付,只要你的系統(tǒng)魯棒性好點(diǎn),基本影響不大。但是nuke的就非常煩,一般來說有兩種思路解決,一種是提高系統(tǒng)魯棒性,另外就是上規(guī)則。我們從這張圖看看兩種思路分布對(duì)應(yīng)的解決效果:
其實(shí)魯棒性的系統(tǒng)做的就是把efficient attack的曲線降低,其實(shí)效果不算太好。
規(guī)則就是做到提前檢測(cè),將危險(xiǎn)扼殺在搖籃之中,就是做的藍(lán)色的那塊detectable的部分。
Fighting spam是個(gè)博大精深的問題,俗話說,與人斗,其樂無窮,就是說的這個(gè)意思。
我們從規(guī)則說來,一般來說規(guī)則可以放到最前面的數(shù)據(jù)收集和過濾的階段,比如在收集數(shù)據(jù)的時(shí)候看看這個(gè)人是否是多個(gè)賬號(hào)但是是一個(gè)ip,或者有些人用戶名或者注冊(cè)郵箱有群體相似性質(zhì),或者有沒有出現(xiàn)pv等不正常的情況。有時(shí)候我們也可以從推薦的結(jié)果里上規(guī)則來查,比如有些人刷的過火了,導(dǎo)致推薦結(jié)果出現(xiàn)一些問題,我們就可以用迭代的方式按照先驗(yàn)的刷子的比例來排出刷子排行榜之類的東西。這些都是經(jīng)驗(yàn)之談,上不了臺(tái)面,大家也可以自己摸索。
結(jié)束:
上面啰嗦了半天,大體上做一個(gè)完整的簡(jiǎn)單推薦系統(tǒng)就是涉及到上面這些步驟。一些地方說的不夠詳細(xì),有些是因?yàn)槲覒,有些是不方便說。大家覺得我說的不對(duì)或者不妥的地方,可以直接在下面跟帖噴,或者有大大指導(dǎo)我也是很歡迎的,大家多交流經(jīng)驗(yàn)。我的郵箱是flclain@gmail.com 豆瓣是 有什么問題也可以豆瓣或者郵件交流。
其實(shí)我是覺得,要是有個(gè)大大說“你個(gè)傻缺,寫的狗屁不通,讓我來教你我是怎么做推薦的”就更好了。
posted @
本文關(guān)鍵詞:推薦系統(tǒng),由筆耕文化傳播整理發(fā)布。
本文編號(hào):50225
本文鏈接:http://sikaile.net/wenshubaike/mishujinen/50225.html