LightGCN實踐2——GPU記憶體爆炸終結篇
hi,GPU記憶體佔滿的問題,這幾天必須解決。這裡issue,暫且相信一次,畢竟分散式GPU訓練太難改模型了。
人生艱難啊。近在眼前都不能抓住,更不要鬼扯遠在天邊的了。
For Video Recommendation in Deep learning QQ Group 277356808
For Visual in deep learning QQ Group 629530787
1-NeuRec開始,
util部分,引數輸入寫的有點繁瑣,我覺得一個argparse完全能夠解決問題。
>>> from configparser import ConfigParser
>>> help(configparser)
ConfigParser -- responsible for parsing a list of
configuration files, and managing the parsed database.
methods:
__init__(defaults=None, dict_type=_default_dict, allow_no_value=False,
delimiters=('=', ':'), comment_prefixes=('#', ';'),
inline_comment_prefixes=None, strict=True,
empty_lines_in_values=True, default_section='DEFAULT',
interpolation=<unset>, converters=<unset>):
Create the parser. When `defaults' is given, it is initialized into the
dictionary or intrinsic defaults. The keys must be strings, the values
must be appropriate for %()s string interpolation.
When `dict_type' is given, it will be used to create the dictionary
objects for the list of sections, for the options within a section, and
for the default values.
When `delimiters' is given, it will be used as the set of substrings
that divide keys from values.
When `comment_prefixes' is given, it will be used as the set of
substrings that prefix comments in empty lines. Comments can be
indented.
之前的一篇已經提及,sys與argparse一起使用比較麻煩。
作者定義的類class Configurator(object)將手動輸入命令列的引數進行了修改,是在讀取config檔案後的修改(_read_config_file)
2-不行啊,使用原來的LightGCN簡單的設定下面引數無濟於事,而且利用率為0,似乎程式不動了,雖然沒有爆炸。
config.gpu_options.per_process_gpu_memory_fraction = 0.8
但等了一會記憶體還是爆炸了。還是這個博文中同樣的錯誤。別告訴我要調成0.5,估計還是會吧【15:08發現還是不行啊】,試試的同時 還是得看完整的NeuRec,那繼續吧。
3-300萬的interaction,50萬user,10萬items,這樣的pre引數計算需要40mins,這是很浪費時間的。如果可以的話我會用這個博文的方法全程加速,先看NeuRec有沒有解決方案。這裡主要是矩陣計算引起的,其中還有稀疏矩陣啥的,比較繁瑣。
大概是從16:08開始計算的,沒想到計算pre引數這麼快?訓練時GPU記憶體也沒有爆炸,一個epoch大概7mins
2020-10-21 16:13:01.396: metrics: Recall@50 NDCG@50 MAP@50 MRR@50
2020-10-21 16:20:29.883: epoch 0: 0.26113239 0.11061251 0.07287365 0.07287365
2020-10-21 16:26:48.926: epoch 1: 0.27756262 0.11437650 0.07370490 0.07370490
後面的指標MAP和MRR相同說明是採用的LOO法做的測試(也就是隻留一個作為測試)
下面剖析程式碼,然後轉化成自己實際可用的——即最終要給訓練的使用者推items列表,這個很多rp都不能實現,包括PaddleRec和tf官方多GPU訓練NCF。實用好用才是最終目的,空有一個花架子沒啥用。
從tmp檔案中可知,編碼後的資料又儲存了,經過檢視發現LOO測試的user個數與test檔案中最大編碼user不等(相差很大)如下
#test檔案最後幾行
563380,18584
563381,317
563382,282291
563384,6313
563385,6053
563386,10545
563387,3450
563390,6366
563391,54
563392,409
#test檔案行數
318620
所以寡人將不採用作者的分割方式,而是直接給定train和test檔案(作者也給出了這種資料讀取方式)
另外編碼方式也不是採用的LabelEncoder,與其結果也不同,即不符合sort的排序方式。如下示例:
#.item2id檔案
7vIByjkyG,0
80dRNWjCi,1
7yGTXZ0Rl,2
7trePsN4W,3
7vWh4RZca,4
7vipDneDA,5
7tsDiXNpM,6
7vI7gzdPo,7
7xh8X1Dx4,8
#user2id
0000851a8cca32,0
0000c50dcf49ab8f0825dce40473dc,1
0000e57cbd8840a33b0e982c468e70,2
0000ee90406d48b8f649ae303212cf,3
其中user符合是因為我本來儲存時已經按照sort方式對user進行了排序。
不知道其中的md5是啥操作,有點神奇。如下:
def check_md5(file_name):
with open(file_name, "rb") as fin:
bytes = fin.read() # read file as bytes
readable_hash = hashlib.md5(bytes).hexdigest()
return readable_hash
#示例
ori_file_md5 = [check_md5(rating_file)]
if os.path.isfile(saved_prefix + ".md5"):
with open(saved_prefix + ".md5", 'r') as md5_fin:
saved_md5 = [line.strip() for line in md5_fin.readlines()]
if ori_file_md5 == saved_md5:
check_state = True
for postfix in [".train", ".test", ".user2id", ".item2id"]:
if not os.path.isfile(saved_prefix + postfix):
check_state = False
經過確認,這個讀取檔案生成的md5字串和儲存的md5檔案中的一樣,這種操作應該是為了確保處理的是同一個資料,下面的要求是確定4個檔案已經生成完畢。
這個有一定的作用,但我將去掉這個。刪繁就簡三秋木,標新立異二月花。將別人的讀懂並改為自己習慣的,這才是看懂了程式碼
重新對映id在我用的LOO是沒有必要的,我將訓練的全部用來test(即給每個user推薦items列表),如果有新的變化,可以在編碼之前就進行這些處理操作,編碼後不再進行二次對映id。原始碼直接採用的序列轉字典的編碼方式,可以。
self.userids = pd.Series(data=range(len(unique_user)), index=unique_user).to_dict()
直接將raw_id作為key,編碼後的為value
>>> kk=np.random.randint(12,17,size=(12,2))
>>> df=pd.DataFrame(kk,columns=['user_id','item_id'])
>>> uids=df['user_id'].unique()
>>> pd.Series(data=range(len(uids)), index=uids).to_dict()
{12: 0, 15: 1, 13: 2}
>>> raw_iid_dict=pd.Series(data=range(len(uids)), index=uids).to_dict()
>>> df['user_id']=df['user_id'].map(raw_iid_dict)
>>> df
user_id item_id
0 0 13
1 1 15
2 2 13
3 1 12
4 1 13
5 2 13
6 2 15
7 2 13
8 2 12
9 1 15
10 0 13
11 2 13
這速度比LabelEncoder快多了啊。
4-關注負取樣部分,原來的gcn程式碼沒有涉及(可能有但沒有特別注意)這裡重點關注
挨個append太慢?這裡extend來直接實現,省去了np.append等類似方法的麻煩。
>>> k
[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
>>> k.extend([1,2,3,4,5])
>>> k
[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1, 2, 3, 4, 5]
在看人程式碼中學習細碎的知識點。很明顯,其中的負取樣與Youtube中採用的方法相同,都是隨機選擇其他使用者的點選item作為負樣本,但這裡另外生成了一個資料集,即每個使用者都有相應的負樣本items列表。
grouped_user = all_data.groupby(["user"])
for user, u_data in grouped_user:
line = [user]
line.extend(randint_choice(self.num_items, size=number_neg,
replace=False, exclusion=u_data["item"].tolist()))
neg_items.append(line)
隨機選擇5個負樣本,程式已經不動了,很慢了,在資料的生成方面有待優化啊。但在實際的LightGCN模型中卻沒有使用負取樣的資料,說明作者還沒有開發這一塊內容。如下:在Dataset類中及LightGCN類中均未找到下面函式的使用
def get_user_test_neg_dict(self):
test_neg_dict = None
if self.negative_matrix is not None:
test_neg_dict = csr_to_user_dict(self.negative_matrix)
return test_neg_dict
dataset = Dataset(conf)
所以小明哥也暫時放棄吧,刪除這部分的程式碼。後來發現的在評估的時候用的這個玩意。所以這部分(其實也是作者重點開發的部分)我也不用了,暫時替換成自己的東西,因為我想在每個epoch只需計算loss,只在最後再計算HR/NDCG,我採用while迴圈做的,速度也不慢。
5-關於引數細節
by_time到底啥意思,其實這個不重要,只要將每個user的item按照time sort即可。split_by_loo
如果點選長度小於等於3(也就是2或者3,沒有2個就不參加訓練了,沒法計算LOO的指標),直接append到first_section中,沒有給他做LOO指標計算,這是上面的3中為啥最大編碼數與行數不同的原因之一(也可能是唯一原因)。
data.dropna(how="any", inplace=True)
dropna(axis=0, how='any', thresh=None, subset=None, inplace=False)
>>> df = pd.DataFrame({"name": ['Alfred', 'Batman', 'Catwoman'],
... "toy": [np.nan, 'Batmobile', 'Bullwhip'],
... "born": [pd.NaT, pd.Timestamp("1940-04-25"),
... pd.NaT]})
>>> df
name toy born
0 Alfred NaN NaT
1 Batman Batmobile 1940-04-25
2 Catwoman Bullwhip NaT
預設去掉其中每行有Na的行,只要行中有Na就去掉,如果how為all,那是行所有的都為Na才去的。
NaN是非數,pd.NaT是非時間,其實也是非數
class NaTType(_NaT)
| (N)ot-(A)-(T)ime, the time equivalent of NaN
train_dict = csr_to_user_dict_bytime(self.time_matrix, self.train_matrix)
if file_format == "UIRT":
self.time_matrix = csr_matrix((train_data["time"], (train_data["user"], train_data["item"])),
shape=(self.num_users, self.num_items))
print("split and save data...")
by_time = config["by_time"] if file_format == "UIRT" else False
因為不是這個形式,而是直接UI形式,那麼這個time矩陣為空,最後一句則是說,只要不是UIRT形式,by_time均為False,手動設定也無效。
6-關於模型
鑑於上面的4,對比原來的GCN中的做法,發現此處程式碼似乎是直接將訓練得到的user和item的embedding做乘積,然後對得到的評分進行argsort,由此終於明白了LightGCN的中心思想,哈哈,有時候還是在程式碼中讀懂啊,這才是讀程式碼的意義。我自己都感覺到了昇華,彷彿一股真氣貫穿任督二脈,所有功力蓄勢待發(哈哈哈,真實體驗就是這樣,就是很開心的感覺)
若是簡單的乘積,那麼必然也可用距離來衡量,因而faiss也就順理成章可以在此使用,也因此可以將所有訓練得到的結果(embedding)儲存下來供faiss召回使用,那速度是相當厲害。從而不需要也不必每小時都要更新,我是這麼覺得。也進一步可以擴大使用者範圍,基本上可以覆蓋客戶端所有使用者,這樣的效果待驗證,敬請持續關注。
舊、新程式碼中的infer部分對比:
self.node_dropout = tf.placeholder(tf.float32, shape=[None])
self.mess_dropout = tf.placeholder(tf.float32, shape=[None])
rate_batch = sess.run(model.batch_ratings, {model.users: user_batch,
model.pos_items: item_batch,
model.node_dropout: [0.] * len(eval(layer_size)),
model.mess_dropout: [0.] * len(eval(layer_size))})
def predict(self, users, candidate_items=None):
feed_dict = {self.users: users}
ratings = self.sess.run(self.batch_ratings, feed_dict=feed_dict)
if candidate_items is not None:
ratings = [ratings[idx][u_item] for idx, u_item in enumerate(candidate_items)]
return ratings
如果後者(新)正確的話,那麼我上面的融會貫通也必然正確,即:對每個使用者都可得到相應的item評分,選取其中最高評分的item即可,這就是推薦完成了。
而Loss的計算則無法體現,而這點不難,只需加個sess.run的引數即可。毫不費力。注意將最終的embedding也sess.run
終於調好了自己的輸入資料,但是有個類PairwiseSampler沒整好,決定放棄,直接引入吧。
下面計算下結果是否正確,HR等指標是否符合要求,隨隨便便也得0.18啊
30萬user結果如下:
[0.03178284 0.15516527 0.04257957 0.05609072 0.01102237]
感覺arg還是慢,不如faiss召回,明天可以試試效果如何。拜拜
相關文章
- 記憶體管理篇——實體記憶體的管理記憶體
- Tensorflow2對GPU記憶體的分配策略GPU記憶體
- JVM記憶體管理——總結篇JVM記憶體
- Android記憶體溢位、記憶體洩漏常見案例分析及最佳實踐總結Android記憶體溢位
- App記憶體優化-實踐APP記憶體優化
- 垃圾回收與記憶體分配——總結篇記憶體
- 讀書筆記2-記憶體優化篇筆記記憶體優化
- C++記憶體模型實踐探索C++記憶體模型
- 2019年終總結之SAP專案實踐篇
- 2018年終總結之SAP專案實踐篇
- Java 8 記憶體管理原理解析及記憶體故障排查實踐Java記憶體
- Oracle面試寶典-記憶體結構篇Oracle面試記憶體
- [轉帖]JVM記憶體配置最佳實踐JVM記憶體
- GPU深度學習效能的三駕馬車:Tensor Core、記憶體頻寬與記憶體層次結構GPU深度學習記憶體
- 實踐App記憶體優化:如何有序地做記憶體分析與優化APP記憶體優化
- 記憶體結構記憶體
- Linux效能優化實戰記憶體篇(五)Linux優化記憶體
- SAP專家培訓之NetweaverABAP記憶體管理和記憶體調優最佳實踐記憶體
- PostgreSQLGPU加速(HeteroDBpg_strom)(GPU計算,GPU-DIO-NvmeSSD,列存,GPU記憶體快取)SQLGPU記憶體快取
- Win10系統GPU共享記憶體怎麼關閉?Win10系統GPU共享記憶體的關閉方法Win10GPU記憶體
- SAP專家培訓之Netweaver ABAP記憶體管理和記憶體調優最佳實踐記憶體
- JVM記憶體結構JVM記憶體
- PostgreSQL:記憶體結構SQL記憶體
- linux記憶體管理(一)實體記憶體的組織和記憶體分配Linux記憶體
- 【Java基礎】實體記憶體&虛擬記憶體Java記憶體
- 【JVM之記憶體與垃圾回收篇】JVM與Java體系結構JVM記憶體Java
- 結構體記憶體對齊結構體記憶體
- Android效能優化篇之記憶體優化--記憶體洩漏Android優化記憶體
- Node除錯指南-記憶體篇除錯記憶體
- 32. DDR2記憶體內部結構-1記憶體
- ArkTS 中的記憶體調優與配置:最佳實踐記憶體
- Redis 記憶體優化在 vivo 的探索與實踐Redis記憶體優化
- Facebook安卓Feed流的記憶體優化實踐安卓記憶體優化
- 重新整理 .net core 實踐篇—————路由和終結點[二十三]路由
- 記憶體管理兩部曲之實體記憶體管理記憶體
- 詳解GPU的記憶體頻寬與CPU的不同GPU記憶體
- ubuntu解決GPU視訊記憶體佔用問題UbuntuGPU記憶體
- Android低記憶體終止守護程式Android記憶體