基於矩陣分解的推薦原理
矩陣分解是推薦系統裏最常用的方法之一,其泛指一類算法,由最初數學中的SVD分解衍變而來,已有衆多個版本,通常而言推薦系統裏的矩陣分解算法指得是FunkSVD分解,可參照這裏(多說一句,這位老師在NLP與推薦系統很多博客緊跟潮流,都值得一看)。關於矩陣分解算法的發展歷史可以參照這裏。
衆多優秀博客珠玉在前,我就不詳談矩陣分解的前世今身了,下面對矩陣分解的思想做個簡單地概況。通常主要用於推薦地數據爲評分矩陣(Rating),此矩陣中每一行代表一個用戶(User)對所有項目(Item)的評分。通常的應用場景中,用戶數目會多於項目數目,這就會導致用戶的評分是稀疏的,即用戶只對少部分項目進行了評分,大部分項目沒有評分。通常,我們把用戶評了分的項目用實際分值填充,在一定場景如評分矩陣爲點擊矩陣時用1填充,而未評分的項目用0填充(事實上,未評分的項目可能喜歡也可能不喜歡,直接爲0是存在問題的,感興趣地可以看這裏),以此得到Rating矩陣。
矩陣分解的思想非常好理解, 直觀上地理解就是將Rating矩陣分解成兩個矩陣的乘法,其中行數和Rating相同的矩陣爲用戶隱因子矩陣,每一行代表一個用戶,每一列代表用戶的潛在特徵;列數和Rating相同的矩陣爲項目隱因子矩陣,每一列代表一個項目,每一行代表項目的潛在特徵。這樣,假設Rating的維度是M×N的,那麼就可以分解爲M×K的User矩陣,K×N的Item矩陣,滿足矩陣Rating和矩陣[User×Item]中的非零項儘可能相等。
基於這個原理,有文章利用公式推導實現了矩陣分解,可以參照這裏。分解後乘出來的近似矩陣中非零元素可以作爲模型對用戶-項目評分的預測,對每一個用戶按分值進行TopK排序即可形成TopK推薦。
矩陣分解與Embedding的關係
自詞向量(Word2Vec)推出以來,各種嵌入(Embedding)方法層出不窮,推薦系統也有部分文章借用Embedding思想進行推薦,Embedding是一種思想,可以理解爲提特徵的手段,萬物皆可Embedding,下面我們來引入這種思想到推薦算法裏。
在NLP領域裏,我們將詞轉化爲K維度的詞向量,再用詞向量去做更爲複雜的NLP任務,如簡單的尋找相關詞裏,就可以直接用詞向量進行相似度計算直觀得到。而在推薦系統的場景裏,我們有用戶和項目兩個主體,假如能將用戶和項目嵌入到同一空間中,再計算相似性,不就直接完成了推薦目的了嗎?
在矩陣分解中,我們同樣也是將User和Item分開,User的每一行,代表用戶的嵌入向量,Item的每一列代表項目的嵌入向量,兩者都在K維空間中,而矩陣乘法的本質就是向量的點積,即User的每一行點乘Item的每一列,而點積a·b = |a||b|cosθ,不就是在計算相似度嗎?
到現在你就會發現,原來矩陣分解就是Embedding的一種,二者殊途同歸。那麼利用神經網絡的框架來實現矩陣分解也就帶來了可能,整體框架如下圖所示。
Keras框架介紹
Keras是一種搭建神經網絡的框架,它封裝得很好,簡便易用,對於新手來講還是十分友好的。它主要包括兩種模型,其一爲序列式模型,即一步步往後走,一條路走到黑;另一種爲函數式模型,適合多輸入;我們的輸入包括User和Item,因此,使用的是函數式模型。
數據集簡介
爲了實驗方便,這裏採用用爛了的豆瓣電影數據集,沒有的可以點擊這裏下載每一行的字段爲:userID, movieID, rating, timestamp。
推薦算法代碼實現
1. 訓練集/測試集劃分
首先,本數據集每個用戶至少訪問了20個電影,算是已預處理過的,現在要進行的就是對訓練集和測試集的劃分,這裏有兩種劃分方法。
第一種,截取每個用戶最後訪問的K個電影做測試集,進行TopK推薦,這樣做的好處在於能確保每個用戶都有屬於他的“標準答案”,但同時也令標準答案的個數固定,導致評價指標召回率和準確率相同。
第二種,按時間進行截取,如通常我們將80%的數據作爲訓練集,20%的數據作爲測試集,那麼就得找到時間戳5分位數的值,大於這個值的當作測試集,反之,當作訓練集。這樣做的好處在於理解簡單,符合直覺,但某些用戶會因時間原因導致沒有“標準答案”,使評價指標不夠客觀。
對於這一方面,我自己還沒有定論,說哪一種一定好,爲了使評價指標的計算更好區分開來,此處選第二種進行劃分。代碼如下:
def split_data(rating,topk):
rating.sort_values(by=['user','time'],axis=0,inplace=True)#先按用戶、再按時間排序
rating['isTest'] = 0 #增加一列,標記是否爲測試數據
rating = rating.reset_index(drop = True)#重新索引
#print(rating)
timestamp = rating['time']
for i in range(1,num_user+1):
rating_ui = rating[rating['user']==i] #用戶i的記錄
idx = rating_ui[rating_ui['time']>=timestamp.quantile(.8)].index#按時間5分位數進行劃分,若改爲最後K個,使用[-topk:].index即可
for j in range(0,len(idx)):#選定的數據標記爲測試集
rating.iloc[idx[j]]['isTest'] = 1
train = rating[rating['isTest']==0]
test = rating[rating['isTest']==1]
return train,test
2. Keras實現矩陣分解模型
本文最初的實現參照了這篇文章,但文中方法有人提出未加正則,且分解出來是負數。針對這兩個問題,進行改進從而得到如下代碼:
def Recmand_model(num_user,num_item,k):
input_uer = Input(shape=[None,],dtype="int32")
model_uer = Embedding(num_user+1,k,input_length = 1,
embeddings_regularizer=regularizers.l2(0.001), #正則,下同
embeddings_constraint=non_neg() #非負,下同
)(input_uer)
model_uer = Dense(k, activation="relu",use_bias=True)(model_uer) #激活函數
model_uer = Dropout(0.1)(model_uer) #Dropout 隨機刪去一些節點,防止過擬合
model_uer = Reshape((k,))(model_uer)
input_item = Input(shape=[None,],dtype="int32")
model_item = Embedding(num_item+1,k,input_length = 1,
embeddings_regularizer=regularizers.l2(0.001),
embeddings_constraint=non_neg()
)(input_item)
model_item = Dense(k, activation="relu",use_bias=True)(model_item)
model_item = Dropout(0.1)(model_item)
model_item = Reshape((k,))(model_item)
out = Dot(1)([model_uer,model_item]) #點積運算
model = Model(inputs=[input_uer,input_item], outputs=out)
model.compile(loss= 'mse', optimizer='Adam')
model.summary()
return model
關於非負,指的是分解後的兩個矩陣每個值都要非負,實現起來比較簡單,Embedding層剛好有進行約束的參數,但思想上還存在一定模糊,電影評分是處於[1-5]的,預測評分爲什麼一定要非負?負數是否可以代表該用戶不喜歡該項目?由於推薦結果實際上只與分值的大小排序有關,非負還是否一定更好?
關於正則,最初的想法是在損失函數那裏改,但發現若自己定義loss,參數只有y_pred而沒有兩個潛在向量,後來仔細一想,在Embedding層的正則,不就剛好是對兩種嵌入向量的正則嘛!於是,利用Embedding層的正則加進去。
除此以外,還嘗試了神經網絡的一些trick,加了一層激活函數,增強模型的非線性性,並使用了偏置和dropout防止過擬合。
3. 模型的訓練
模型的構建有三個參數,用戶數、項目數和嵌入向量的維度,而模型的輸入爲訓練集的用戶記錄數據、項目記錄數據和真實評分。batch_size是批處理參數,epochs是模型的迭代次數,h5爲HDF5文件格式。
def train(train_data):
model = Recmand_model(num_user,num_item,100)
train_user = train_data['user'].values
train_item = train_data['item'].values
train_x = [train_user,train_item]
train_y = train_data['score'].values
model.fit(train_x,train_y,batch_size = 100,epochs =10)
model.save("model.h5")
算法測試與評價
1. 評價指標
本文計算了推薦系統常用的性能評價指標:RMSE、MAE、PRE、REC、MAP、NDCG、MRR。大部分評價指標都是分類或信息檢索領域演化而來的。RMSE與MAE是對預測分值的評價指標,如若只是對最終的推薦結果進行評價則無需此項。剩下的評價中MAP與NDCG比較難理解,且大多數的解釋都是用信息檢索那一套解釋的,MAP可以看這裏,NDCG可以看這裏,其他的可以看這裏。
通常而言,比較重要的指標是PRE、REC、MAP,其中MAP既要求命中,又要求順序與原順序儘量相等,所以往往較低。
2. 評價指標的實現
評價指標往往通過計算單個用戶的值再用所有用戶求平均,因此單獨寫了個求每個用戶各項指標值的函數如下:
def cal_indicators(rankedlist, testlist,test_score):
hits = 0
sum_precs = 0
AP_u = 0
PRE_u = 0
REC_u = 0
NDCG_u = 0
MRR_u = 0
ranked_score = []
for n in range(len(rankedlist)):
if rankedlist[n] in testlist:
hits += 1
sum_precs += hits / (n + 1.0)
ranked_score.append(test_score[testlist.index(rankedlist[n])])
if MRR_u == 0:
MRR_u = float(1/(testlist.index(rankedlist[n])+1)) #測試集用的是時間序而非評分序
else:
ranked_score.append(0)
if hits > 0:
AP_u = sum_precs/len(testlist)
PRE_u = float(hits/len(rankedlist))
REC_u = float(hits/len(testlist))
DCG_u = cal_DCG(ranked_score)
IDCG_u = cal_DCG(sorted(test_score)[0:len(rankedlist)])
NDCG_u = DCG_u/IDCG_u
return AP_u,PRE_u,REC_u,NDCG_u,MRR_u
爲了計算NDCG方便,將DCG的計算公式也單獨做成函數
def cal_DCG(rec_list):
s = 0
for i in range(0,len(rec_list)):
s = s + (math.pow(2,rec_list[i])-1)/math.log2((i+1)+1)
return s
3. 模型的讀取與測試
參數中的all_user,all_item分別爲用戶集合和項目集合。在整個測試中,要注意,儘管TopK推薦結果是依據模型的打分,但真正在推薦時,要剔除用戶曾經訪問過的項目,推薦往往是挖掘用戶更深層次的需求而不是一味地推薦給他訪問過的項目,這是推薦和預測的區別。
def test(train_data,test_data,all_user,all_item,topk):
model = load_model('model.h5')
RMSE = 0
MAE = 0
PRE = 0
REC = 0
MAP = 0
NDCG = 0
MRR = 0
for i in range(0,len(all_user)):
visited_item = list(train_data[train_data['user']==all_user[i]]['item'])
# print(visited_item)
testlist = list(test_data[test_data['user']==all_user[i]]['item'])
rat_k = list(test_data[test_data['user']==all_user[i]]['score']) #項目的評分
p_rating = [] #總的預測評分
rankedlist = [] #項目推薦列表
for j in range(0,len(all_item)): #讓每個用戶給所有項目打分
p_rating.append(float(model.predict([[all_user[i]],[all_item[j]]])))
MAE = MAE + sum([abs(rat_k[s]-p_rating[s]) for s in range(len(testlist))])
RMSE = RMSE + sum([(rat_k[s]-p_rating[s])*(rat_k[s]-p_rating[s]) for s in range(len(testlist))])
k = 0
while k < topk:#取前topK個
idx = p_rating.index(max(p_rating))
if all_item[idx] in visited_item: #排除掉訪問過的
p_rating[idx] = 0
continue
rankedlist.append(all_item[idx])
p_rating[idx] = 0
k = k + 1
print("對用戶",all_user[i])
print("Topk推薦:",rankedlist)
print("實際訪問:",testlist)
AP_u,PRE_u,REC_u,NDCG_u,MRR_u = cal_indicators(rankedlist, testlist,rat_k)
PRE = PRE + PRE_u
REC = REC + REC_u
MAP = MAP + AP_u
NDCG = NDCG + NDCG_u
MRR = MRR + MRR_u
print('--------')
print('評價指標如下:')
RMSE = math.sqrt(RMSE/float(len(test_data)))
MAE = MAE/float(len(test_data))
PRE = PRE/len(all_user)
REC = REC/len(all_user)
MAP = MAP/len(all_user)
NDCG = NDCG/len(all_user)
MRR = MRR/len(all_user)
print('RMSE:',RMSE)
print('MAE:',MAE)
print('PRE@',topk,':',PRE)
print('REC@',topk,':',REC)
print('MAP@',topk,':',MAP)
print('NDCG@',topk,':',NDCG)
print('MRR@',topk,':',MRR)
4.主程序
此次實驗做Top10推薦,且與這篇文章效果進行對比。
if __name__ == '__main__':
rating = pd.read_csv('movie.txt',header = None,sep = '\t',names = ['user','item','score','time'])
all_user = np.unique(rating['user'])
num_user = len(all_user)
all_item = np.unique(rating['item'])
num_item = len(np.unique(rating['item']))
num_record = len(rating)
topk = 10
# print("用戶數爲:",num_user,"項目數爲:",num_item,"記錄數爲:",num_record)
# filling_rate = num_record/(num_user*num_item)
# print("填充率爲:",filling_rate)
train_data,test_data = split_data(rating,topk) #分割訓練集、測試集
train(train_data)
test(train_data,test_data,all_user,all_item,topk)
實驗結果分析
原文方法實驗結果:
本文方法實驗結果:
從實驗結果上來看,加入正則、激活函數、非負約束、偏置、dropout等等之後,還是有一定提高的,用神經網絡的架構實現矩陣分解,一方面成熟的框架能提高運行速度,另一方面,更多的Embedding和深度學習技巧可以運用進來,給傳統的矩陣分解創造更多可能!
完整代碼可看我的GitHub,點擊這裏。
後期問題
在後期做更豐富的實驗的時候出現了一些問題(但源程序運行時是沒產生的),會陸續在這裏更新、總結一下,爲日後警醒自己,就不直接去更改了。
DataFrame 賦值問題
在split_data()的第11行,爲了給某行某列具體的項賦值時,使用了這行代碼:
rating.iloc[idx[j]]['isTest'] = 1
在做擴展實驗時,出現了一個Warnning,而且這個Warnning導致賦值不成功:
A value is trying to be set on a copy of a slice from a DataFrame
查了下解決方法,使用 DafaFrameming.loc[行名, 列名] = 值 的方式去賦值, 而不是使用DataFrame[][]的形式去賦值。改爲如下語句即可:
rating.loc[idx[j],'isTest'] = 1
對此的解釋可以參照這裏。
參數傳遞問題
還是以split_data()爲例,在第7行代碼中用到了變量num_user,然而此變量是在main函數裏定義的,在函數裏也沒有作爲參數傳遞過來,又不是全局變量。本以爲自己寫錯了,內存沒清乾淨纔沒報錯,結果重啓編譯器,一輸出還能輸出出來,值也沒問題,就有點迷惑python變量的命名空間了,因爲沒有產生問題,就沒有改,但還是希望自己能理解。
效率問題
矩陣分解其實效率不高,此文中所用數據集較小所以沒出啥毛病,但在較大(唯一用戶數量或唯一項目數量)數據集上,要麼顯示資源耗盡,要麼就訓練時長無法接受。此時還可以調的參數是batch_size和嵌入維度k,同樣影響有限,這是方法本身的侷限。
性能分析
從推薦列表結果來看,矩陣分解給大部分用戶推薦的項目“幾乎相同”,矩陣分解的推薦可能更着重於用戶羣體整體性偏好 (如微博熱搜、頭條等),個性化方面不足。
實驗中推薦對象問題
這裏同訓練集、測試集的劃分中指出的問題類似,如果採用按時間(或記錄條數)劃分數據集,那麼必然會有一些用戶在測試集中未訪問任何項目,此時相當於沒有正確答案。如果將其計入評價指標(即也算一次推薦),則出來的評價結果實際上會偏低。因此,實驗中推薦的對象應爲測試集中出現過的用戶,確保每個用戶至少有一個標準答案。
反映在代碼中即將test()函數中的all_user進行如下賦值:
all_user = np.unique(test_data['user'])
有效性驗證
在更復雜的數據集實踐中,發現此方法實現的MF效果並不好,症狀表現在給大多數用戶推薦相同的項目。爲了驗證這篇文章提出的基於神經網絡的矩陣分解方法(簡稱NMF),和傳統梯度下降實現的Basic矩陣分解方法(簡稱BMF),特加一個小實驗進行驗證。
驗證過程不專門使用數據集,而是隨便用一個小型矩陣R,驗證指標設爲e,其代表R中每一個非零項,與MF後矩陣nR對應項的差值平方的累加和。
R是隨便設的,這裏展示的R如下圖所示:
先來看下NMF的有效性驗證,下圖爲維度d設爲5,迭代次數設爲1000次的運行結果:
可以看到nR中原始矩陣的非零值很接近真實值了,說明至少矩陣分解的效果是達到了。
再看看NMF和BMF對比實驗的結果,先上迭代次數爲500,嵌入維度設爲5,比較100次的結果:
運行 100 次 NMF獲勝次數: 2 BMF獲勝次數: 98
NMF最小e: 50.98786165386436 NMF最大e: 52.702692984687744
BMF最小e: 7.8951470117335205 BMF最大e: 65.59012435806488
再上迭代次數爲1000時,嵌入維度設爲5,比較100次的結果:
運行 100 次 NMF獲勝次數: 65 BMF獲勝次數: 35
NMF最小e: 6.627555767762395 NMF最大e: 25.706345176698093
BMF最小e: 3.503323177984534 BMF最大e: 29.463017445662206
從實驗結果看,NMF相比於BMF在迭代次數達到一定值後還是有優勢的,當然,這個e值只能簡單地反饋出一定信息,只能保證更接近原始值,空缺值是否更接近真實值還得專門從已有的數據集按評價指標進行分析。做完這個測試後,覺得問題可能還是出現在迭代次數上,將繼續改進自己的複雜實驗。
附測試代碼如下:
# -*- coding: utf-8 -*-
"""
Created on Fri Oct 18 15:08:00 2019
@author: YLC
"""
import os
import numpy as np
import pandas as pd
import time
import math
from keras import Model
import keras.backend as K
from keras.layers import Embedding,Reshape,Input,Dot,Dense,Dropout,concatenate
from keras.models import load_model
from keras.utils import to_categorical
from keras import regularizers
from keras.constraints import non_neg
def Recmand_model(num_user,num_item,d):
K.clear_session()
input_uer = Input(shape=[None,],dtype="int32")
model_uer = Embedding(num_user+1,d,input_length = 1,
embeddings_regularizer=regularizers.l2(0.001), #正則,下同
embeddings_constraint=non_neg() #非負,下同
)(input_uer)
# model_uer = Dense(d, activation="relu",use_bias=True)(model_uer) #激活函數
# model_uer = Dropout(0.1)(model_uer) #Dropout 隨機刪去一些節點,防止過擬合
model_uer = Reshape((d,))(model_uer)
input_item = Input(shape=[None,],dtype="int32")
model_item = Embedding(num_item+1,d,input_length = 1,
embeddings_regularizer=regularizers.l2(0.001),
embeddings_constraint=non_neg()
)(input_item)
# model_item = Dense(d, activation="relu",use_bias=True)(model_item)
# model_item = Dropout(0.1)(model_item)
model_item = Reshape((d,))(model_item)
out = Dot(1)([model_uer,model_item]) #點積運算
model = Model(inputs=[input_uer,input_item], outputs=out)
model.compile(loss= 'mse', optimizer='Adam')
model.summary()
return model
def train(num_user,num_item,train_data,d,step):
model = Recmand_model(num_user,num_item,d)
train_user = train_data[:,0]
train_item = train_data[:,1]
train_x = [train_user,train_item]
train_y = train_data[:,2]
model.fit(train_x,train_y,batch_size = 4,epochs = step)
model.save("./MFmodel.h5")
def test(num_user,num_item,R):
model = load_model('./MFmodel.h5')
nR = np.zeros([num_user,num_item])
for i in range(num_user):
for j in range(num_item):
nR[i][j] = model.predict([[i],[j]])
return nR
def cal_e(R,nR):
e = 0
for i in range(len(R)):
for j in range(len(R[0])):
if(R[i][j]!=0):
e = e + math.pow(R[i][j]-nR[i][j],2)
return e
def RtransT(R):
user = [u for u in range(len(R))]
item = [i for i in range(len(R[0]))]
Table = []
for i in user:
for j in item:
if R[i][j]!= 0:
Table.append([i,j,R[i][j]])
Table = np.array(Table)
return Table
def matrix_factorization(R,P,Q,K,steps=500,alpha=0.0002,beta=0.02):
Q=Q.T
for step in range(steps):
for i in range(len(R)):
for j in range(len(R[i])):
if R[i][j]>0:
eij=R[i][j]-np.dot(P[i,:],Q[:,j])
for k in range(K):
P[i][k]=P[i][k]+alpha*(2* eij * Q[k][j]- beta *P[i][k])
Q[k][j]=Q[k][j]+alpha*(2* eij * P[i][k]- beta *Q[k][j])
eR=np.dot(P,Q)
e=0
for i in range(len(R)):
for j in range(len(R[i])):
if R[i][j]>0:
e=e+pow(R[i][j]-np.dot(P[i,:],Q[:,j]),2)
for k in range(K):
e=e+(beta/2)*(pow(P[i][k],2)+pow(Q[k][j],2))
if e<0.001:
break
return P,Q.T,e
def NMF(R,d,step):
T = RtransT(R)
M=len(R)
N=len(R[0])
train(M,N,T,d,step)
nR = test(M,N,R)
e = cal_e(R,nR)
print(nR)
print(e)
return e
def BMF(R,K,step):
M=len(R)
N=len(R[0])
P=np.random.uniform(low=0,high=5,size=[M,K])
Q=np.random.uniform(low=0,high=5,size=[N,K])
#P=np.random.rand(M,K)
#Q=np.random.rand(N,K)
nP,nQ,_= matrix_factorization(R,P,Q,K,steps=step)
nR=np.dot(nP,nQ.T)
print(nR)
e = cal_e(R,nR)
print(e)
return e
def eval_NB(R,test_cnt,emd_cnt,step):
Ne = []
Be = []
Nwin = 0
Bwin = 0
for i in range(test_cnt):
ne = NMF(R,emd_cnt,step)
be = BMF(R,emd_cnt,step)
Ne.append(ne)
Be.append(be)
if(ne<be):
Nwin = Nwin + 1
else:
Bwin = Bwin + 1
print("運行",test_cnt,'次',"NMF獲勝次數:",Nwin,"BMF獲勝次數:",Bwin)
print("NMF最小e:",min(Ne),"NMF最大e:",max(Ne))
print("BMF最小e:",min(Be),"BMF最大e:",max(Be))
with open('./result_.txt','w') as f:
f.write("運行"+str(test_cnt)+'次'+"NMF獲勝次數:"+str(Nwin)+"BMF獲勝次數:"+str(Bwin)+'\n')
f.write("NMF最小e:"+str(min(Ne))+"NMF最大e:"+str(max(Ne)))
f.write("BMF最小e:"+str(min(Be))+"BMF最大e:"+str(max(Be)))
f.close()
if __name__ == '__main__':
R=[
[5,3,0,1,2,4,5,2,1,2,5,0],
[4,0,0,1,1,2,0,0,0,0,3,1],
[1,1,0,5,2,4,5,1,2,3,0,0],
[1,0,0,4,5,1,2,0,0,0,0,2],
[0,1,5,4,2,3,0,0,0,2,1,2]
]
R=np.array(R)
dimension = 5
step = 1000
# NMF(R,dimension,step)
# BMF(R,dimension,step)
eval_NB(R,100,dimension,step)
過擬合
經過後續反覆嘗試,發現在複雜數據集上對每個用戶推薦相同項目的原因是過擬合。因爲做的是隱式反饋,用戶訪問過就是1,未訪問過就是0,用NMF進行訓練時,loss確實很小,但觀測預測的矩陣nR發現,矩陣所有位置都是0.999+,接近1,而loss是算原先不爲0的地方,因此,loss是小了,但模型根本沒用。
通過實驗發現神經網絡雖然與部分方法理論相同,但實際中會遇到各式各樣的問題,不能盲目迷信,理智地掌握訓練時地“trick”,或許才能更近一步吧。
求助
問題描述:在使用傳統矩陣分解方法進行推薦時發現,迭代次數越多(loss越小),準確率越低,並且一直呈現單調變化,準確率最高是在迭代1次的時候,PRE、REC能達到0.5幾,但loss卻很大。LOSS確實在下降說明梯度下降的方向沒有錯,而結果表明越接近原始非零值,效果越不好,這是爲什麼?問題可能出現在哪裏?