1.9-改進的CBOW模型的實現

文章目錄

  • 0引言
  • 1 CBOW模型的重構
    • 1.1模型初始化
    • 1.2模型的前向計算
    • 1.3模型的反向傳播
  • 2總結

0引言

  1. 前面講述了對word2vec高速化的改進:
    1. 改進輸入側的計算,變成Embedding,即從權重矩陣中選取特定的行;
    2. 改進輸出側的計算,包含兩點
      1. 改進輸出側矩陣乘法,改為Embedding_dot層,Embedding部分其實與輸入側一樣;dot部分就是將中間層的結果與Embedding部分的結果做內積得到一個值;
      2. 化多分類為二分類,將softmax改進為sigmoid,并引入負采樣方法;損失函數依然使用交叉熵損失,只不過是二分類的;
  2. 接下來,將這兩塊的改進應用到CBOW模型上,重新構建CBOW模型以及學習代碼。

1 CBOW模型的重構

代碼位于:improved_CBOW/CBOW.py;代碼文件鏈接:https://1drv.ms/u/s!AvF6gzVaw0cNjqNRnWXdF3J6J0scCA?e=3mfDlx;

1.1模型初始化

  1. 截止模型初始化,程序入口的代碼如下:

    if __name__ == "__main__":text = "you say goodbye and I say hello."# 構建單詞與編號之間的映射并將句子向量化corpus, word_to_id, id_to_word = preprocess(text)# contexts是一個維度為[6,2]的numpy數組contexts = np.array([[0, 2], [1, 3], [2, 4], [3, 1], [4, 5], [1, 6]])  # (6,2)target = np.array([1, 2, 3, 4, 1, 5])  # (6,)vocab_size = len(word_to_id)hidden_size = 3window_size = 1CBOW_model = CBOW(vocab_size, hidden_size, window_size, corpus)
    
  2. 改進之后CBOW模型的初始化代碼如下:

    class CBOW:def __init__(self, vocab_size, hidden_size, window_size, corpus):V, H = vocab_size, hidden_size# 初始化權重W_in = 0.01 * np.random.randn(V, H).astype('f') # (7,3)# 因為W_out這里將使用embedding層,計算時需要轉置,# 所以這里索性初始化就直接是轉置后的W_out = 0.01 * np.random.randn(V, H).astype('f') # (7,3)# 生成層self.in_layers = []for i in range(2 * window_size):layer = Embedding(W_in)  # 使用Embedding層self.in_layers.append(layer)self.ns_loss = NegativeSamplingLoss(W_out, corpus, power=0.75, sample_size=3)# 將所有的權重和梯度整理到列表中layers = self.in_layers + [self.ns_loss]self.params, self.grads = [], []for layer in layers:self.params += layer.paramsself.grads += layer.grads# 將單詞的分布式表示設置為成員變量self.word_vecs = W_in
    
  3. 關于初始化的代碼,做如下解釋:

    1. 因為W_out這里將使用Embedding層,前面的筆記中說過,計算時需要轉置,所以這里索性初始化就直接是轉置后的;因此從代碼上來看,輸入側的權重和輸出側的權重維度相同,在學習的過程中分別去優化;
    2. 和之前一樣,根據上下文窗口的大小,生成相應數量的輸入層;只是這里改進之后,創建的是相應數量的Embedding層
    3. 根據負采樣的sample_size,為每個負例創建相應的sigmoid層以及交叉熵損失計算層;為正例創建一個sigmoid層以及交叉熵損失計算層
  4. 再來看一下初始化的結果:

    1. 如下圖:創建的CBOW_model包含輸入層in_layers、輸出側的Embedding_dot層ns_loss.embed_dot_layers、sigmoid&交叉熵損失計算層ns_loss.loss_layers

      在這里插入圖片描述

    2. 每一個Embedding_dot層都包含一個Embedding層,其中的參數維度都是(vocab_size,hidden_size);如下圖所示:

      在這里插入圖片描述

    3. 經過整理,所有的參數和梯度都被整理到一塊,如下圖所示;前兩個是輸入側的兩個權重矩陣的參數(因為上下文窗口大小為1),后面四個是輸出側一個正例和三個負例的權重矩陣的參數;梯度跟參數對應,這里就不列了;

      在這里插入圖片描述

1.2模型的前向計算

  1. 為了進行前向計算,程序入口增加的代碼如下:

    loss = CBOW_model.forward(contexts, target) # contexts:(6,2);target:(6,)
    
  2. 前向計算的代碼如下:

    def forward(self, contexts, target):'''@param contexts: 目標詞的上下文;(batch_size, 2*window_size);e.g. (6,2)@param target: 目標詞;(batch_size,);e.g. (6,)'''h = 0for i, layer in enumerate(self.in_layers):h += layer.forward(contexts[:, i]) # h:(6,3)h *= 1 / len(self.in_layers) # 對h進行平均;window_size不一定是1,所以取決于self.in_layersloss = self.ns_loss.forward(h, target)return loss
    
  3. 關于輸入側的計算:

    1. 每次計算一個mini-batch的上下文的某一個單詞的前向計算結果,因此每次傳入的是contexts[:, i],維度是(6,);這是一個mini-batch的單詞ID,forward方法會從該layer的權重矩陣中抽取對應的行,返回的結果就是(6,3)h
    2. 由于我們只改變了輸入側的計算方法,輸入側的計算結果仍然像之前一樣,求平均;因此需要對所有輸入層的中間結果求平均得到總的h
  4. 接著,在self.ns_loss.forward中,首先進行負例采樣;根據傳入的target,為其中每一個樣本抽取sample_size個負例樣本對應的單詞ID,得到negative_sample,維度為(batch_size,sample_size)

  5. 接著,在self.ns_loss.forward中,進行正例的前向計算;將這一個mini-batch的正例從輸出側的權重矩陣中抽取對應的行,并于對應的中間結果做內積,得到這個mini-batch的得分,維度為(batch_size,);例如(6,);然后將這個得分和真實標簽一起送入sigmoid&損失計算層,計算交叉熵損失得到損失值;這個損失值是一個標量,是一個mini-batch損失的平均值;

  6. 接著,在self.ns_loss.forward中,進行負例的前向計算;計算過程與正例一樣;但因為每個樣本的有sample_size個負例,因此一次同時處理一個mini-batch的某一個負例;然后將所有負例的損失累加的正例的損失中,作為最終的前向計算的損失值;

  7. 輸出以及損失側的計算步驟較多,這里再貼出來loss = self.ns_loss.forward(h, target)的具體過程:

    def forward(self, h, target):'''@param h: 中間層的結果,維度為(batch_size,hidden_dim); e.g. (6,3)@param target: 正確解標簽;維度為(batch_size,); e.g. (6,)'''batch_size = target.shape[0]# 獲取self.sample_size個負例解標簽negative_sample = self.sampler.get_negative_sample(target) # (batch_size,sample_size); e.g. (6,3)# 正例的正向傳播score = self.embed_dot_layers[0].forward(h, target) # (batch_size,) e.g. (6,)correct_label = np.ones(batch_size, dtype=np.int32) # 正例的真實標簽自然是1;維度為(batch_size,) e.g. (6,)loss = self.loss_layers[0].forward(score, correct_label) # 損失標量# 負例的正向傳播negative_label = np.zeros(batch_size, dtype=np.int32) # 負例的真實標簽自然是0;維度為(batch_size,) e.g. (6,)for i in range(self.sample_size):# 對一個mini-batch的每一個負例樣本,依次計算損失并累加到正例的損失上去negative_target = negative_sample[:, i] # (batch_size,) e.g. (6,)score = self.embed_dot_layers[1 + i].forward(h, negative_target) # (batch_size,)loss += self.loss_layers[1 + i].forward(score, negative_label)return loss
    

1.3模型的反向傳播

  1. 為了進行反向傳播,程序入口增加的代碼如下:

    CBOW_model.backward()
    
  2. 先進行輸出側的反向傳播:

    1. 輸出側由一個正例+sample_size個負例組成,根據計算圖,它們求得的輸出層的輸入側的梯度需要進行累加;

    2. 因此遍歷所有的loss_layersembed_dot_layers,然后先進行loss_layer的反向傳播,再進行embed_dot_layer的反向傳播;

      1. 因為前向計算時每個損失是累加起來作為最終損失的,因此反向傳播時傳到每個損失里面的dout=1;于是就根據之前推導的結果,sigmoid+交叉熵損失的梯度是y-t,計算傳遞至loss_layer輸入側的梯度;
      2. 然后計算embed_dot_layer的反向傳播;計算對dtarget_w的梯度以更新這個Embedding_dot層的權重參數;計算dh以將梯度傳遞至下游;過程在前面的筆記中講解過;
    3. 由于過程較多,因此這里貼出來代碼供查看;另外注釋也更新了;

      # 輸出側損失反向傳播入口
      dout = self.ns_loss.backward(dout)
      # 輸出側損失反向傳播入口對應的反向傳播函數
      def backward(self, dout=1):dh = 0# 中間層結果h到輸出側是進入了多個分支,因此反向傳播時梯度需要累加for l0, l1 in zip(self.loss_layers, self.embed_dot_layers):# 依次對正例和每個負例所在的網絡結構進行反向傳播dscore = l0.backward(dout) # 損失函數(sigmoid和交叉熵損失)的反向傳播,即y-t的結果;維度為(batch_size,); e.g. (6,)dh += l1.backward(dscore) # Embedding_dot的反向傳播,包含保存各自權重矩陣對應的行的梯度return dh# sigmoid函數的反向傳播
      def backward(self, dout=1):'''本質上是對sigmoid函數的輸入求梯度'''batch_size = self.t.shape[0]dx = (self.y - self.t) * dout / batch_size # 這里將梯度平均了;維度為(batch_size,); e.g. (6,)return dx# Embedding_dot層的反向傳播
      def backward(self,dout):'''@param dout: 上游損失函數的梯度;形狀為(batch_size,);e.g. (6,)'''h,target_w=self.cachedout=dout.reshape(dout.shape[0],1) # 這里是為了保證dout的形狀與h的形狀一致;形狀為(batch_size,1);e.g. (6,1)dtarget_w=dout*h # 對應元素相乘;dout:[batch_size,1];h:[batch_size,hid_dim];所以會進行廣播;形狀為(batch_size,hidden_dim);e.g. (6,3)self.embed.backward(dtarget_w) # 把梯度更新到權重矩陣的梯度矩陣的對應行;先前在執行self.embed.forward(idx)時已經保存了使用的idxdh=dout*target_w # 對應元素相乘;會進行廣播;形狀為(batch_size,hidden_dim);e.g. (6,3)return dh
      
  3. 然后是中間層的梯度,因為前向計算時,是對window_size個輸入層的輸出結果平均了,才得到的h;所以執行如下語句計算中間層的梯度;

    dout *= 1 / len(self.in_layers)
    
  4. 最后,計算window_size個輸入層的梯度,即Embedding層;由于只是從輸入側權重矩陣中選取了特定行,因此梯度的傳播僅僅是將上游傳遞來的梯度值放到對應的梯度矩陣中;代碼如下:

    for layer in self.in_layers:layer.backward(dout)
    

2總結

  1. 幾點注意
    1. 關于這里使用的batch_size的含義:個人理解,這里的批處理大小并不是指通常意義的樣本數(or句子數),CBOW模型每次的輸入就是目標詞的上下文單詞;一個目標詞對應的上下文單詞構成mini-batch里面的一條數據;
    2. 改進之前,輸入和輸出都是使用的獨熱編碼,改進之后,不再使用;

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/44340.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/44340.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/44340.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Perl中的文件系統守衛:實現自定義訪問控制

🛡? Perl中的文件系統守衛:實現自定義訪問控制 在系統編程中,文件系統訪問控制是確保數據安全和完整性的關鍵機制。Perl作為一種功能強大的腳本語言,提供了豐富的接口來實現自定義的文件系統訪問控制。本文將深入探討如何在Perl…

【C語言】【排序算法】----- 歸并排序

由于最近要考試,好久沒有發博客了,非常抱歉大家對我的支持。之后我會不斷更新博客,繼續創作出高質量的文章,希望能幫到大家! 文章目錄 一、歸并排序基本思想二、遞歸實現三、非遞歸實現四、效率分析 一、歸并排序基本…

Foxit Reader:高效、安全、多功能的PDF閱讀器技術解析

引言 在當今數字化時代,PDF(Portable Document Format)文檔已成為工作、學習和生活中不可或缺的一部分。作為處理PDF文件的重要工具,PDF閱讀器的選擇顯得尤為關鍵。今天,我們將深入探討一款備受推崇的PDF閱讀器——Fo…

KDP數據分析實戰:從0到1完成數據實時采集處理到可視化

智領云自主研發的開源輕量級Kubernetes數據平臺,即Kubernetes Data Platform (簡稱KDP),能夠為用戶提供在Kubernetes上的一站式云原生數據集成與開發平臺。在最新的v1.1.0版本中,用戶可借助 KDP 平臺上開箱即用的 Airflow、AirByte、Flink、K…

MySQL數據庫中利用定時作業去殺死長時查詢以防止數據庫死鎖風險

MySQL數據庫中沒有SQLServer數據庫中那種傳統的定時作業的概念。但是提供了一種【事件】的東西,基本和定時作業貌離神合。 下面我們在MySQL中創建一個事件,它的作用是去監測時間很長的異常查詢,并且去主動殺掉該線程以防止數據庫發生死鎖的風…

探索Perl的自動清潔工:垃圾收集機制全解析

🧹 探索Perl的自動清潔工:垃圾收集機制全解析 Perl是一種高級編程語言,以其強大的文本處理能力而聞名。在Perl中,內存管理對于開發高效且穩定的應用程序至關重要。Perl提供了自動垃圾收集機制,幫助開發者管理內存&…

關于原型和原型鏈的學習和實踐

在前端面試中,原型和原型鏈始終是一個避不開的問題,今天就弄明白! 原型和原型鏈 對象的創建方式工廠模式構造函數模式原型模式 原型和原型鏈實踐 對象的創建方式 原型和原型鏈都是關于對象的內容,先來看一下JavaScript中對象的構建方式。 工…

代碼隨想錄(day3)有序數組的平方

暴力求解法: 注意:需要確定范圍,比如nums.sort()是在for循環之外,根據函數的功能來確定 return返回的是nums,而不是nums[i]因為返回的是整個數組 class Solution(object):def sortedSquares(self, nums):for i in r…

人話學Python-基礎篇-數字計算

一:數字類型 對于最常見的數據類型,數字在Python中分為三類: 整型(int) 表示的是整數類型的所有數字,包括正整數,負整數和0。和C語言不同的是,Python中的int型沒有范圍的限制,理論上可以從無限小的整數取到…

RedHat運維-Ansible自動化運維基礎22-rhel-system-roles

1. system_roles的官方文檔的位置是___________________________________; 2. system_roles的官方文檔的位置是___________________________________; 3. system_roles的官方文檔的位置是___________________________________; 4. 安裝rhel-s…

react基礎語法,模板語法,ui渲染,jsx,useState狀態管理

創建一個react應用 這里使用create-react-app的腳手架構建項目(結構簡潔,基于webpack-cli), npx create-react-app [項目名稱] 使用其他腳手架構建項目可以參考:react框架,使用vite和nextjs構建react項目…

數學建模國賽入門指南

文章目錄 認識數學建模及國賽認識數學建模什么是數學建模?數學建模比賽 國賽參賽規則、評獎原則如何評省、國獎評獎規則如何才能獲獎 國賽賽題分類及選題技巧國賽賽題特點賽題分類 國賽歷年題型及優秀論文數學建模分工技巧數模必備軟件數模資料文獻數據收集資料收集…

力扣題解(乘積為正數的最長子數組長度)

1567. 乘積為正數的最長子數組長度 已解答 中等 給你一個整數數組 nums ,請你求出乘積為正數的最長子數組的長度。 一個數組的子數組是由原數組中零個或者更多個連續數字組成的數組。 請你返回乘積為正數的最長子數組長度。 本題要求乘積為正數,而整…

白蛇插畫:成都亞恒豐創教育科技有限公司

白蛇插畫:古韻今風,情深意長 在浩瀚的藝術長河中,插畫作為一種獨特的藝術形式,以其生動形象的畫面、豐富多彩的色彩和深邃悠遠的意境,成都亞恒豐創教育科技有限公司深受人們喜愛。而“白蛇插畫”,作為融合…

bug - while parsing file included at

bug 如下 找到這個對應文件tb_top.sv的對應行,發現是一個 include "inc_tb_tests_xxx.sv" 問題點:頭文件,重復定義,那么 解決方法- 在被include的文件首尾加入 ifndef MY_TRANSACTION__SV define MY_TRANSACTION__SV …

GenAI 技術堆棧架構師指南 - 十種工具

這篇文章于 2024 年 6 月 3 日首次出現在 The New Stack 上。 我之前寫過關于現代數據湖參考架構的文章,解決了每個企業面臨的挑戰——更多的數據、老化的Hadoop工具(特別是HDFS)以及對RESTful API(S3)和性能的更大需求…

《javascript語言精粹》學習筆記之函數特性

分析javascript javascript比較好的思想:函數、弱類型、動態對象、對象字面量表示法 不好的思想:基于全局變量的編程模型 函數 函數對象 函數就是對象,新創建的函數會連接到Function.prototype上,沒和函數創建時附帶有兩個隱藏…

前端--第一個前端程序

第一個前端程序 第一步: 使用記事本,編寫代碼 在你的一個磁盤里面創建一個文件夾,名為前端,然后在里面新建一個記事本,在里面寫如下代碼,注意一定要使用英文,然后把后綴名稱改為.html。 第二…

你明白C++中的多態嗎?(暑假提升-多態專題)

內不欺己,外不欺人。———孔子 有趣的多態 1、前言2、概念3、多態定義與產生條件4、多態的重要組成成員-(虛函數)5、虛函數的重寫(覆蓋)6、輔助關鍵字override與final(了解即可)7、重載,重定義(隱藏),重寫(覆蓋)8、抽象類9、多態的原理9、1、…

PHP老照片修復文字識別圖像去霧一鍵摳圖微信小程序源碼

🔍解鎖復古魅力,微信小程序黑科技大揭秘!老照片修復&更多神奇功能等你來試! 📸 【老照片修復,時光倒流的美顏術】 你是否珍藏著一堆泛黃的老照片,卻因歲月侵蝕而模糊不清?現在…