zoukankan      html  css  js  c++  java
  • [课堂笔记][pytorch学习][2]词向量模型

    第二节课笔记:

    词向量是一种分布式表示

    由论文Distributed Representations of Words and Phrases and their Compositionality提出

    具体原理可看斯坦福公开课cs224n的第一节讲的简单易懂

    import torch
    import torch.nn as nn  #神经网络工具箱torch.nn 
    import torch.nn.functional as F  #神经网络函数torch.nn.functional
    import torch.utils.data as tud  #Pytorch读取训练集需要用到torch.utils.data

    两个模块的区别:torch.nn 和 torch.functional 的区别

    简略说明:

    torch. nn.Module中实现layer的都是一个特殊的类,可以去查阅,他们都是以class xxxx来定义的,会自动提取可学习的参数
    而 nn.functional中的函数,更像是纯函数,由def function( )定义,只是进行简单的 数学运算而已。
    说到这里你可能就明白二者的区别了,functional中的函数是一个确定的不变的运算公式,输入数据产生输出就ok,
    而深度学习中会有很多权重是在不断更新的,不可能每进行一次forward就用新的权重重新来定义一遍函数来进行计算,所以说就会采用类的方式,以确保能在参数发生变化时仍能使用我们之前定好的运算步骤。
    所以从这个分析就可以看出什么时候改用nn.Module中的layer了:
    如果模型有可学习的参数,最好使用nn.Module对应的相关layer,否则二者都可以使用,没有什么区别。
    比如此例中的Relu其实没有可学习的参数,只是进行一个运算而已,所以使用的就是functional中的relu函数,
    而卷积层和全连接层都有可学习的参数,所以用的是nn.Module中的类。
    不具备可学习参数的层,将它们用函数代替,这样可以不用放在构造函数中进行初始化。
    from torch.nn.parameter import Parameter  #参数更新和优化函数
    from collections import Counter #Counter 计数器 import numpy as np import random import math import pandas as pd import scipy #SciPy是基于NumPy开发的高级模块,它提供了许多数学算法和函数的实现 import sklearn from sklearn.metrics.pairwise import cosine_similarity #余弦相似度函数
    USE_CUDA = torch.cuda.is_available()
    
    # 为了保证实验结果可以复现,我们经常会把各种random seed固定在某一个值
    random.seed(53113)
    np.random.seed(53113)
    torch.manual_seed(53113)
    if USE_CUDA:
        torch.cuda.manual_seed(53113)
        
    # 设定一些超参数
        
    K = 100 # number of negative samples负样本随机采样
    C = 3 # nearby words threshold指定周围三个单词进行预测
    NUM_EPOCHS = 2 # The number of epochs of training
    MAX_VOCAB_SIZE = 30000 # the vocabulary size
    BATCH_SIZE = 128 # the batch size
    LEARNING_RATE = 0.2 # the initial learning rate
    EMBEDDING_SIZE = 100
           
        
    LOG_FILE = "word-embedding.log"
    
    # tokenize函数,把一篇文本转化成一个个单词
    def word_tokenize(text):
        return text.split()
    • 从文本文件中读取所有的文字,通过这些文本创建一个vocabulary
    • 由于单词数量可能太大,我们只选取最常见的MAX_VOCAB_SIZE个单词
    • 我们添加一个UNK单词表示所有不常见的单词
    • 我们需要记录单词到index的mapping,以及index到单词的mapping,单词的count,单词的(normalized) frequency,以及单词总数。
    with open("text8.train.txt", "r") as fin: #读入文件
        text = fin.read()
        
    text = [w for w in word_tokenize(text.lower())] 
    #分词,在这里类似于text.split()
    
    vocab = dict(Counter(text).most_common(MAX_VOCAB_SIZE-1))
    #字典格式,把(MAX_VOCAB_SIZE-1)个最频繁出现的单词取出来,-1是留给不常见的单词
    
    vocab["<unk>"] = len(text) - np.sum(list(vocab.values()))
    #unk表示不常见单词数=总单词数-常见单词数
    #这里计算的到vocab["<unk>"]=29999
    
    idx_to_word = [word for word in vocab.keys()] 
    #取出字典的所有单词key
    
    word_to_idx = {word:i for i, word in enumerate(idx_to_word)}
    #取出所有单词的单词和对应的索引,索引值与单词出现次数相反,最常见单词索引为0。
    
    word_counts = np.array([count for count in vocab.values()], dtype=np.float32)
    #所有单词的频数values
    
    word_freqs = word_counts / np.sum(word_counts)
    #所有单词的频率
    
    word_freqs = word_freqs ** (3./4.)
    #论文里乘以3/4次方
    
    word_freqs = word_freqs / np.sum(word_freqs) # 用来做 negative sampling
    # 重新计算所有单词的频率
    
    VOCAB_SIZE = len(idx_to_word) #词汇表单词数30000=MAX_VOCAB_SIZE

    Dataloader

    这里有一个好的tutorial介绍如何使用PyTorch dataloader

    有了dataloader之后,我们可以轻松随机打乱整个数据集,拿到一个batch的数据等等。

    class WordEmbeddingDataset(tud.Dataset): #tud.Dataset父类
        def __init__(self, text, word_to_idx, idx_to_word, word_freqs, word_counts):
            ''' text: a list of words, all text from the training dataset
                word_to_idx: the dictionary from word to idx
                idx_to_word: idx to word mapping
                word_freq: the frequency of each word
                word_counts: the word counts
            '''
            super(WordEmbeddingDataset, self).__init__() #初始化模型
            self.text_encoded = [word_to_idx.get(t, VOCAB_SIZE-1) for t in text]
            #字典 get() 函数返回指定键的值(第一个参数),如果值不在字典中返回默认值(第二个参数)。
            #取出text里每个单词word_to_idx字典里对应的索引,不在字典里返回"<unk>"的索引
            #"<unk>"的索引=29999,get括号里第二个参数应该写word_to_idx["<unk>"],不应该写VOCAB_SIZE-1,虽然数值一样。
            
            self.text_encoded = torch.Tensor(self.text_encoded).long()
            #变成tensor类型,这里变成longtensor,也可以torch.LongTensor(self.text_encoded)
            
            self.word_to_idx = word_to_idx #保存数据
            self.idx_to_word = idx_to_word  #保存数据
            self.word_freqs = torch.Tensor(word_freqs) #保存数据
            self.word_counts = torch.Tensor(word_counts) #保存数据
            
        def __len__(self): #数据集有多少个item 
            #魔法函数__len__
            ''' 返回整个数据集(所有单词)的长度
            '''
            return len(self.text_encoded) #所有单词的总数
            
        def __getitem__(self, idx):
            #魔法函数__getitem__,这个函数跟普通函数不一样
            ''' 这个function返回以下数据用于训练
                - 中心词
                - 这个单词附近的(positive)单词
                - 随机采样的K个单词作为negative sample
            '''
            center_word = self.text_encoded[idx] 
            #print(center_word)
            #中心词索引
            #这里__getitem__函数是个迭代器,idx代表了所有的单词索引。
            
            pos_indices = list(range(idx-C, idx)) + list(range(idx+1, idx+C+1))
            #周围词索引的索引,比如idx=0时。pos_indices = [-3, -2, -1, 1, 2, 3] 
            #老师讲这里的时候,我不是特别明白
            
            pos_indices = [i%len(self.text_encoded) for i in pos_indices]
            #range(idx+1, idx+C+1)超出词汇总数时,需要特别处理,取余数
            
            pos_words = self.text_encoded[pos_indices]
            #周围词索引,就是希望出现的正例单词
            #print(pos_words)
            
            neg_words = torch.multinomial(self.word_freqs, K * pos_words.shape[0], True)
            #负例采样单词索引,torch.multinomial作用是对self.word_freqs做K * pos_words.shape[0]次取值,输出的是self.word_freqs对应的下标。
            #取样方式采用有放回的采样,并且self.word_freqs数值越大,取样概率越大。
            #每个正确的单词采样K个,pos_words.shape[0]是正确单词数量
            #print(neg_words)
            
            return center_word, pos_words, neg_words 

    创建dataset和dataloader

    dataset = WordEmbeddingDataset(text, word_to_idx, idx_to_word, word_freqs, word_counts)

    # list(dataset) 可以把尝试打印下center_word, pos_words, neg_words看看

    
    
    dataloader = tud.DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=4) 

    定义PyTorch模型

    class EmbeddingModel(nn.Module):
        def __init__(self, vocab_size, embed_size):
            ''' 初始化输出和输出embedding
            '''
            super(EmbeddingModel, self).__init__()
            self.vocab_size = vocab_size  #30000
            self.embed_size = embed_size  #100
            
            initrange = 0.5 / self.embed_size
            self.out_embed = nn.Embedding(self.vocab_size, self.embed_size, sparse=False)
            #模型输出nn.Embedding(30000, 100)
            self.out_embed.weight.data.uniform_(-initrange, initrange)
            #权重初始化的一种方法initrange (初始范围)
            
            
            self.in_embed = nn.Embedding(self.vocab_size, self.embed_size, sparse=False)
             #模型输入nn.Embedding(30000, 100)
            self.in_embed.weight.data.uniform_(-initrange, initrange)
            #权重初始化的一种方法
            
            
        def forward(self, input_labels, pos_labels, neg_labels):
            '''
            input_labels: 中心词, [batch_size]
            pos_labels: 中心词周围 context window 出现过的单词 [batch_size * (window_size * 2)]
            neg_labelss: 中心词周围没有出现过的单词,从 negative sampling 得到 [batch_size, (window_size * 2 * K)]
            
            return: loss, [batch_size]
            '''
            
            batch_size = input_labels.size(0)  #input_labels是输入的标签,tud.DataLoader()返回的。相已经被分成batch了。
            
            input_embedding = self.in_embed(input_labels) 
            # B * embed_size
            #这里估计进行了运算:(128,30000)*(30000,100)= 128(B) * 100 (embed_size)
            
            pos_embedding = self.out_embed(pos_labels) # B * (2*C) * embed_size
            #同上,增加了维度(2*C),表示一个batch有B组周围词单词,一组周围词有(2*C)个单词,每个单词有embed_size个维度。
            
            neg_embedding = self.out_embed(neg_labels) # B * (2*C * K) * embed_size
            #同上,增加了维度(2*C*K)
          
        
            #torch.bmm()为batch间的矩阵相乘(b,n.m)*(b,m,p)=(b,n,p)
            log_pos = torch.bmm(pos_embedding, input_embedding.unsqueeze(2)).squeeze() # B * (2*C)
            log_neg = torch.bmm(neg_embedding, -input_embedding.unsqueeze(2)).squeeze() # B * (2*C*K)
            #unsqueeze(2)指定位置升维,.squeeze()压缩维度。
            
            #下面loss计算就是论文里的公式
            log_pos = F.logsigmoid(log_pos).sum(1)
            log_neg = F.logsigmoid(log_neg).sum(1) # batch_size     
            loss = log_pos + log_neg
            
            return -loss
        
        def input_embeddings(self):   #取出self.in_embed数据参数
            return self.in_embed.weight.data.cpu().numpy()
            
  • 相关阅读:
    mysql自动补齐
    重置oracle 11G的system、sys密码《亲测》
    细说业务逻辑(前篇)<转>
    当我们把这么一个“狭义的概念”与“这个概念本身”等同起来时,误会、迷茫、困惑、不屑就出现了。
    设计模式就三个准则
    面向对象编程(OOP)的三大特点
    在计算机中,“透明”一词的理解。
    关于ArrayList中添加对象的一个有趣问题~~~~
    MyEclipse 代码自动提示功能失效 提示No Default Proposals 或 no completions available 的解决方法
    面向对象的三大基本特征
  • 原文地址:https://www.cnblogs.com/nakkk/p/14988007.html
Copyright © 2011-2022 走看看