Python 迁移学习实用指南:6~11(2)

简介: Python 迁移学习实用指南:6~11(2)

Python 迁移学习实用指南:6~11(1)https://developer.aliyun.com/article/1426853

我们可以对该语料进行预处理,并将每个文档转换为单词标记序列。 为此,我们使用nltk。 然后,我们可以开始进行如下训练。 我们使用了大量的迭代,因此需要 6-8 个小时的时间来训练 CPU:

# tokenize sentences in corpus
wpt = nltk.WordPunctTokenizer()
tokenized_corpus = [wpt.tokenize(document.lower()) for document in corpus]
w2v_model = word2vec.Word2Vec(tokenized_corpus, size=50, 
                              window=10, min_count=5,
                              sample=1e-3, iter=1000)

现在让我们看看该模型学到了什么。 让我们从这个语料库中选择一些见解的词。 可以在以下位置找到电影评论中通常使用的大量意见词。 我们首先将找到与这些给定单词具有相似嵌入的前五个单词。 以下是此代码:

similar_words = {search_term: [item[0] for item in w2v_model.wv.most_similar([search_term], topn=5)]
                for search_term in ['good','superior','violent',  
                                    'romantic','nasty','unfortunate',   
                                    'predictable', 'hilarious', 
                                    'fascinating', 'boring','confused', 
                                    'sensitive',   
                                    'imaginative','senseless',   
                                    'bland','disappointing']}
pd.DataFrame(similar_words).transpose()

前面的代码的输出如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R80HBEhr-1681567330244)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/ac5407ea-187e-44b0-9709-449e7be7bdcc.png)]

我们可以看到,学习到的嵌入表示具有相似嵌入向量的相似上下文中使用的单词。 这些词不必一直都是同义词,它们也可以相反。 但是,它们在类似的上下文中使用。

GloVe 模型

GloVe 模型代表全局向量,它是一种无监督的学习模型,可用于获取类似于 Word2Vec 的密集词向量。 但是,该技术不同,并且对聚合的全局单词-单词共现矩阵执行训练,从而为我们提供了具有有意义子结构的向量空间。 该方法发表在 Pennington 及其合作者的论文《GloVe:用于词表示的全局向量》。 我们已经讨论了基于计数的矩阵分解方法,例如潜在语义分析LSA)和预测方法,例如 Word2vec。 本文声称,目前这两个家庭都遭受重大弊端。 像 LSA 之类的方法可以有效地利用统计信息,但是它们在词类比任务上的表现相对较差-我们是如何找到语义相似的词的。 像 skip-gram 这样的方法在类比任务上可能会做得更好,但它们在全局级别上却很少利用语料库的统计信息。

GloVe 模型的基本方法是首先创建一个庞大的单词-上下文共现矩阵,该矩阵由(单词,上下文)对组成,这样该矩阵中的每个元素都代表一个单词在上下文中出现的频率(可以是一个单词序列)。 这个词-语境矩阵WC与在各种任务的文本分析中普遍使用的单词-文档矩阵非常相似。 矩阵分解用于将矩阵WC表示为两个矩阵的乘积。 字特征WF)矩阵和特征上下文FC)矩阵。 WC = WF x FC。 用一些随机权重初始化WFFC,然后将它们相乘得到WC'(近似于WC)并测量与WC有多近。 我们使用随机梯度下降SGD)进行多次操作,以最大程度地减少误差。 最后,WF矩阵为我们提供了每个单词的单词嵌入,其中F可以预设为特定数量的维。 要记住的非常重要的一点是,Word2vec 和 GloVe 模型在工作方式上非常相似。 他们两个的目的都是建立一个向量空间,每个词的位置根据其上下文和语义而受到其相邻词的影响。 Word2vec 从单词共现对的本地单个示例开始,而 GloVe 从整个语料库中所有单词的全局汇总共现统计开始。

在以下各节中,我们将同时使用 Word2vec 和 GloVe 来解决各种分类问题。 我们已经开发了一些工具代码,可从文件读取和加载 GloVe 和 Word2vec 向量,并返回嵌入矩阵。 预期的文件格式是标准 GloVe 文件格式。 以下是几个单词的五维嵌入格式示例:单词后跟向量,所有空格分开:

  • 甩动7.068106 -5.410074 1.430083 -4.482612 -1.079401
  • -1.584336 4.421625 -12.552878 4.940779 -5.281123
  • 侧面0.461367 4.773087 -0.176744 8.251079 -11.168787
  • 恐怖7.324110 -9.026680 -0.616853 -4.993752 -4.057131

以下是读取 GloVe 向量的主要函数,给定一个词汇表作为 Python 字典,字典键作为词汇表中的单词。 仅需要为我们的训练词汇中出现的单词加载所需的嵌入。 同样,用所有嵌入的均值向量和一些白噪声来初始化 GloVe 嵌入中不存在的词汇。 01行专用于空格和语音外OOV)单词。 这些单词不在词汇表中,而是在语料库中,例如非常少见的单词或一些过滤掉的杂音。 空间的嵌入是零向量。 OOV 的嵌入是所有其余嵌入的均值向量:

def _init_embedding_matrix(self, word_index_dict, 
                           oov_words_file='OOV-Words.txt'):
        # reserve 0, 1 index for empty and OOV
        self.embedding_matrix = np.zeros((len(word_index_dict)+2 ,            
                                         self.EMBEDDING_DIM)) 
        not_found_words=0
        missing_word_index = []
        with open(oov_words_file, 'w') as f: 
            for word, i in word_index_dict.items():
                embedding_vector = self.embeddings_index.get(word)
                if embedding_vector is not None:
                    # words not found in embedding index will be all-
                      zeros.
                    self.embedding_matrix[i] = embedding_vector
                else:
                    not_found_words+=1
                    f.write(word + ','+str(i)+'\n')
                    missing_word_index.append(i)
            #oov by average vector:
            self.embedding_matrix[1] = np.mean(self.embedding_matrix, 
                                                axis=0)
for indx in missing_word_index:
                self.embedding_matrix[indx] =   
                                    np.random.rand(self.EMBEDDING_DIM)+  
                                               self.embedding_matrix[1]
        print("words not found in embeddings:   
              {}".format(not_found_words))

另一个工具函数是update_embeddings。 这是转学的必要条件。 我们可能希望将一个模型学习的嵌入更新为另一模型学习的嵌入:

def update_embeddings(self, word_index_dict, other_embedding, other_word_index):
        num_updated = 0
        for word, i in other_word_index.items():
            if word_index_dict.get(word) is not None:
                embedding_vector = other_embedding[i]
                this_vocab_word_indx = word_index_dict.get(word)
                self.embedding_matrix[this_vocab_word_indx] =  
                                                       embedding_vector 
                num_updated+=1
        print('{} words are updated out of {}'.format(num_updated,  
               len(word_index_dict)))

CNN 文件模型

先前我们看到了词嵌入如何能够捕获它们表示的概念之间的许多语义关系。 现在,我们将介绍一个 ConvNet 文档模型,该模型可构建文档的分层分布式表示形式。 这发表在 Misha Denil 等人的论文中。 该模型分为两个级别,一个句子级别和一个文档级别,这两个级别都使用 ConvNets 实现。 在句子级别,使用 ConvNet 将每个句子中单词的嵌入转换为整个句子的嵌入。 在文档级别,另一个 ConvNet 用于将句子嵌入转换为文档嵌入。

在任何 ConvNet 架构中,卷积层之后都是子采样/池化层。 在这里,我们使用 k-max 池。 k-max 合并操作与正常 max 合并略有不同,后者从神经元的滑动窗口获取最大值。 在 k-max 合并操作中,最大的k神经元取自下一层中的所有神经元。 例如,对[3, 1, 5, 2]应用 2-max 合并将产生[3, 5]。 在这里,内核大小为 3 且步幅为 1 的常规最大池将得到相同的结果。 让我们来考虑另一种情况。 如果我们对[1, 2, 3, 4, 5]应用最大池,则将得到[3, 5],但是 2-max 池将给出[4, 5]。 K-max 池可以应用于可变大小的输入,并且我们仍然可以获得相同数量的输出单元。

下图描述了卷积神经网络CNN)架构。 我们已针对各种用例对该结构进行了一些微调,将在此处进行讨论:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XNdlY7b2-1681567330244)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-tl-py/img/91149c74-17ef-432b-8b3a-d7449fc50523.png)]

此网络的输入层未在此处显示。 输入层按顺序是文档中的句子序列,其中每个句子由单词索引序列表示。 以下代码段描述了在给定训练语料库的情况下如何定义单词索引。 索引 0 和 1 保留用于空字和 OOV 字。 首先,将语料库中的文档标记为单词。 非英语单词被过滤掉。 同样,计算整个语料库中每个单词的频率。 对于大型语料库,我们可以从词汇表中过滤掉不常用的词。 然后,为词汇表中的每个单词分配一个整数索引:

from nltk.tokenize import sent_tokenize, wordpunct_tokenize
import re
corpus = ['The cat sat on the mat . It was a nice mat !',
         'The rat sat on the mat . The mat was damaged found at 2 places.']
vocab ={}
word_index = {}
for doc in corpus:
    for sentence in sent_tokenize(doc):
        tokens = wordpunct_tokenize(sentence)
        tokens = [token.lower().strip() for token in tokens]
        tokens = [token for token in tokens 
                      if re.match('^[a-z,.;!?]+$',token) is not None ]
        for token in tokens:
            vocab[token] = vocab.get(token, 0)+1
# i= 0 for empty, 1 for OOV
i = 2
for word, count in vocab.items():
    word_index[word] = i
    i +=1
print(word_index.items())
#Here is the output:
dict_items([('the', 2), ('cat', 3), ('sat', 4), ('on', 5), ('mat', 6), ('.', 7), ('it', 8), ('was', #9), ('a', 10), ('nice', 11), ('!', 12), ('rat', 13), ('damaged', 14), ('found', 15), ('at', 16), ('places', 17)])

现在,可以将语料库转换为单词索引数组。 在语料库中,不同的句子和文档的长度不同。 尽管卷积可以处理任意宽度的输入,但是为了简化实现,我们可以为网络定义一个固定大小的输入。 我们可以将短句子置零,并截断较长的句子以适应固定的句子长度,并在文档级别执行相同的操作。 在下面的代码片段中,我们显示了如何使用keras.preprocessing模块对句子和文档进行零填充并准备数据:

from keras.preprocessing.sequence import pad_sequences
SENTENCE_LEN = 10; NUM_SENTENCES=3;
for doc in corpus:
    doc2wordseq = []
    sent_num =0
    for sentence in sent_tokenize(doc):
        words = wordpunct_tokenize(sentence)
        words = [token.lower().strip() for token in words]
        word_id_seq = [word_index[word] if word_index.get(word) is not    
         None \
                                        else 1 for word in words]
        padded_word_id_seq = pad_sequences([word_id_seq], 
                                            maxlen=SENTENCE_LEN,
                                            padding='post',
                                            truncating='post')
        if sent_num < NUM_SENTENCES:
            doc2wordseq = doc2wordseq + list(padded_word_id_seq[0]) 
    doc2wordseq = pad_sequences([doc2wordseq],  
                                 maxlen=SENTENCE_LEN*NUM_SENTENCES,
                                 padding='post',
                                 truncating='post')
    print(doc2wordseq)
# sample output
[ 2 3 4 5 2 6 7 0 0 0 8 9 10 11 6 12 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[ 2 13 4 5 2 6 7 0 0 0 2 6 9 14 15 16 1 17 7 0 0 0 0 0 0 0 0 0 0 0]

因此,您可以看到每个文档输入都是一个尺寸为doc_length = SENTENCE_LEN * NUM_SENTENCES的一维张量。 这些张量通过网络的第一层,即嵌入层,以将单词索引转换为密集的单词表示形式,然后得到形状为doc_length×embedding_dimension的二维张量。 所有先前的预处理代码都捆绑在Preprocess类中,并具有fittransform方法,例如scikit模块。 fit方法将训练语料库作为输入,构建词汇表,并为词汇表中的每个单词分配单词索引。 然后,可以使用transform方法将测试或保留集转换为填充的单词索引序列,如先前所示。 transform方法将使用由fit计算的单词索引。

可以使用 GloVe 或 Word2vec 初始化嵌入矩阵。 在这里,我们使用了 50 维 GloVe 嵌入来初始化嵌入矩阵。 在 GloVe 和 OOV 单词中找不到的单词初始化如下:

  • OOV 单词-训练数据词汇(索引 1)中排除的单词由所有 GloVe 向量的均值初始化
  • 在 GloVe 中找不到的单词由所有 Glove 向量和相同维数的随机向量的均值初始化

以下代码段在之前讨论的 GloVe class_init_embedding_matrix方法中具有相同的功能:

#oov by average vector:
self.embedding_matrix[1] = np.mean(self.embedding_matrix, axis=0)
for indx in missing_word_index:
    self.embedding_matrix[indx] = np.random.rand(self.EMBEDDING_DIM)+  
                                  self.embedding_matrix[1]

初始化嵌入矩阵之后,我们现在可以构建第一层,即嵌入层,如下所示:

from keras.layers import Embedding
embedding_layer = Embedding(vocab_size,
                            embedding_dim,
                            weights=[embedding_weights],
                            input_length=max_seq_length,
                            trainable=True,
                            name='embedding')

接下来,我们必须构建单词卷积层。 我们希望在所有句子上应用相同的一维卷积滤波器,也就是说,在所有句子之间共享相同的卷积滤波器权重。 首先,我们使用Lambda层将输入分成句子。 然后,如果我们使用 C 卷积滤波器,则每个句子的二维张量形状(SENTENCE_LEN×EMBEDDING _DIM)将转换为(SENTENCE_LEN-filter + 1) × C张量。 以下代码执行相同的操作:

#Let's take sentence_len=30, embedding_dim=50, num_sentences = 10
#following convolution filters to be used for all sentences.
word_conv_model = Conv1D(filters= 6,
                         kernel_size= 5,
                         padding="valid",
                         activation="relu", 
                         trainable = True,
                         name = "word_conv",
                         strides=1)
for sent in range(num_sentences):
    ##get one sentence from the input document
    sentence = Lambda(lambda x : x[:, sent*sentence_len: 
                                         (sent+1)*sentence_len, :])(z)
    ##sentence shape : (None, 30, 50) 
    conv = word_conv_model(sentence) 
    ## convolution shape : (None, 26, 6)

k-max 合并层在keras中不可用。 我们可以将 k-max 池实现为自定义层。 要实现自定义层,我们需要实现三种方法:

  • call(x):这是实现层的逻辑的地方
  • compute_output_shape(input_shape):如果自定义层修改了其输入的形状
  • build(input_shape):定义层权重(我们不需要此,因为我们的层没有权重)

这是 k-max 合并层的完整代码:

import tensorflow as tf
from keras.layers import Layer, InputSpec
class KMaxPooling(Layer):
     def __init__(self, k=1, **kwargs):
        super().__init__(**kwargs)
        self.input_spec = InputSpec(ndim=3)
        self.k = k
    def compute_output_shape(self, input_shape):
        return (input_shape[0], (input_shape[2] * self.k))
    def call(self, inputs):
        # swap last two dimensions since top_k will be 
        # applied along the last dimension
        shifted_input = tf.transpose(inputs, [0, 2, 1])
        # extract top_k, returns two tensors [values, indices]
        top_k = tf.nn.top_k(shifted_input, k=self.k, sorted=True, 
                             name=None)[0]
        # return flattened output
        return top_k

将前面的 k-max 池化层应用于单词卷积,我们得到句子嵌入层:

for sent in range(num_sentences):
    ##get one sentence from the input document
    sentence = Lambda(lambda x : x[:,sent*sentence_len: 
                                         (sent+1)*sentence_len, :])(z)
    ##sentence shape : (None, 30, 50) 
    conv = word_conv_model(sentence) 
    ## convolution shape : (None, 26, 6)
    conv = KMaxPooling(k=3)(conv)
    #transpose pooled values per sentence
    conv = Reshape([word_filters*sent_k_maxpool,1])(conv)
    ## shape post k-max pooling and reshape (None, 18=6*3, 1)

因此,我们将形状为30×50的每个句子转换为18×1,然后将这些张量连接起来以获得句子嵌入。 我们使用 Keras 中的Concatenate层来实现相同的功能:

z = Concatenate()(conv_blocks) if len(conv_blocks) > 1 else conv_blocks[0]
z = Permute([2,1], name='sentence_embeddings')(z)
## output shape of sentence embedding is : (None, 10, 18)

如前所述,对前一句子嵌入应用一维卷积,然后进行 k-max 合并,以获得文档嵌入。 这样就完成了文本的文档模型。 根据手头的学习任务,可以定义下一层。 对于分类任务,可以将文档嵌入连接到密集层,然后连接到具有K单元的最终 softmax 层,以解决 k 类分类问题。 在最后一层之前,我们可以有多个致密层。 以下代码段实现了相同的功能:

sent_conv = Conv1D(filters=16,
                   kernel_size=3,
                   padding="valid",
                   activation="relu",
                   trainable = True,
                   name = 'sentence_conv',
                   strides=1)(z)
z = KMaxPooling(k=5)(sent_conv)
z = Flatten(name='document_embedding')(z)
for i in range(num_hidden_layers):
    layer_name = 'hidden_{}'.format(i)
    z = Dense(hidden_dims, activation=hidden_activation, 
               name=layer_name)(z)
model_output = Dense(K, activation='sigmoid',name='final')(z)

整个代码包含在cnn_document_model模块中。

建立评论情感分类器

现在,通过训练前面的 CNN 文档模型来构建情感分类器。 我们将使用“亚马逊情感分析评论”数据集来训练该模型。 该数据集由数百万个 Amazon 客户评论(输入文本)和星级(输出标签)组成。 数据格式如下:标签,后跟空格,审阅标题,后跟:和空格,位于审阅文本之前。 该数据集比流行的 IMDB 电影评论数据集大得多。 此外,此数据集包含各种产品和电影的相当多的评论集:

__label__<X> <summary/title>: <Review Text>
Example:
__label__2 Good Movie: Awesome.... simply awesome. I couldn't put this down and laughed, smiled, and even got tears! A brand new favorite author.

在此,__label__1对应于 1 星和 2 星评论,__label__2对应于 4 星和 5 星评论。 但是,此数据集中未包含三星级评论,即具有中性情感的评论。 在此数据集中,我们总共有 360 万个训练示例和 40 万个测试示例。 我们将从训练示例中随机抽取一个大小为 200,000 的样本,以便我们可以猜测一个很好的超参数来进行训练:

train_df = Loader.load_amazon_reviews('train')
print(train_df.shape)
test_df = Loader.load_amazon_reviews('test')
print(test_df.shape)
dataset = train_df.sample(n=200000, random_state=42)
dataset.sentiment.value_counts()

接下来,我们使用Preprocess类将语料库转换为填充的单词索引序列,如下所示:

preprocessor = Preprocess()
corpus_to_seq = preprocessor.fit(corpus=corpus)
holdout_corpus = test_df['review'].values
holdout_target = test_df['sentiment'].values
holdout_corpus_to_seq = preprocessor.transform(holdout_corpus)

让我们使用GloVe类用 GloVe 初始化嵌入,并构建文档模型。 我们还需要定义文档模型参数,例如卷积过滤器的数量,激活函数,隐藏单元等。 为了避免网络的过拟合,我们可以在输入层,卷积层甚至最终层或密集层之间插入丢弃层。 同样,正如我们在密集层所观察到的,放置高斯噪声层可作为更好的正则化器。 可以使用以下定义的所有这些参数初始化DocumentModel类。 为了对模型参数进行良好的初始化,我们从少量的周期和少量的采样训练示例开始。 最初,我们开始使用六个词卷积过滤器(如针对 IMDB 数据的论文所述),然后发现该模型不适合-训练的精度未超过 80%,然后我们继续缓慢地增加词过滤器的数量 。 同样,我们发现了大量的句子卷积过滤器。 我们尝试了卷积层的 ReLU 和 tanh 激活。 如论文所述,他们将 tanh 激活用于其模型:

glove=GloVe(50)
initial_embeddings = glove.get_embedding(preprocessor.word_index)
amazon_review_model =  
  DocumentModel(vocab_size=preprocessor.get_vocab_size(),
                             word_index = preprocessor.word_index,
                             num_sentences = Preprocess.NUM_SENTENCES, 
                             embedding_weights = initial_embeddings,
                             conv_activation = 'tanh',
                             hidden_dims=64, 
                             input_dropout=0.40, 
                             hidden_gaussian_noise_sd=0.5)

以下是此模型的参数的完整列表,我们已将其用于训练了 360 万个完整的训练示例:

{  
    "embedding_dim":50,
    "train_embedding":true,
    "sentence_len":30,
    "num_sentences":10,
    "word_kernel_size":5,
    "word_filters":30,
    "sent_kernel_size":5,
    "sent_filters":16,
    "sent_k_maxpool":3,
    "input_dropout":0.4,
    "doc_k_maxpool":4,
    "sent_dropout":0,
    "hidden_dims":64,
    "conv_activation":"relu",
    "hidden_activation":"relu",
    "hidden_dropout":0,
    "num_hidden_layers":1,
    "hidden_gaussian_noise_sd":0.5,
    "final_layer_kernel_regularizer":0.0,
    "learn_word_conv":true,
    "learn_sent_conv":true
 }

最后,在开始全面训练之前,我们需要确定一个好的批次大小。 对于大批量(如 256),训练非常慢,因此我们使用了64的批量。 我们使用rmsprop优化器来训练我们的模型,并从keras使用的默认学习率开始。 以下是训练参数的完整列表,它们存储在TrainingParameters类中:

{"seed":55,
      "batch_size":64,
      "num_epochs":35,
      "validation_split":0.05,
      "optimizer":"rmsprop",
      "learning_rate":0.001}

以下是开始训练的代码:

train_params = TrainingParameters('model_with_tanh_activation')
amazon_review_model.get_classification_model().compile(
                                                loss="binary_crossentropy", 
                                                optimizer=
                                                  train_params.optimizer,
                                                metrics=["accuracy"])
checkpointer = ModelCheckpoint(filepath=train_params.model_file_path,
                               verbose=1,
                               save_best_only=True,
                               save_weights_only=True)
x_train = np.array(corpus_to_seq)
y_train = np.array(target)
x_test = np.array(holdout_corpus_to_seq)
y_test = np.array(holdout_target)
amazon_review_model.get_classification_model().fit(x_train, y_train, 
                      batch_size=train_params.batch_size, 
                      epochs=train_params.num_epochs,
                      verbose=2,
                      validation_split=train_params.validation_split,
                      callbacks=[checkpointer])

我们已经在 CPU 上训练了该模型,下面是五个周期后的结果。 对于 190k 样本,只有一个周期非常慢,大约需要 10 分钟才能运行。 但是,您可以在下面看到,在五个周期之后的训练和验证准确率达到 92%,这是相当不错的:

Train on 190000 samples, validate on 10000 samples
 Epoch 1/35
 - 577s - loss: 0.3891 - acc: 0.8171 - val_loss: 0.2533 - val_acc: 0.8369
 Epoch 2/35
 - 614s - loss: 0.2618 - acc: 0.8928 - val_loss: 0.2198 - val_acc: 0.9137
 Epoch 3/35
 - 581s - loss: 0.2332 - acc: 0.9067 - val_loss: 0.2105 - val_acc: 0.9191
 Epoch 4/35
 - 640s - loss: 0.2197 - acc: 0.9128 - val_loss: 0.1998 - val_acc: 0.9206
 Epoch 5/35
 ...
 ...

我们对 40 万条评论进行了评估,对模型进行了评估,结果的准确率也达到 92%。 这清楚地表明该模型非常适合此审阅数据,并且随着数据的增加,还有更多的改进空间。 到目前为止,在整个训练过程中,迁移学习的主要用途是用于初始化单词嵌入的 GloVe 嵌入向量。 在这里,由于我们拥有大量数据,因此我们可以从头开始学习权重。 但是,让我们看看在整个训练过程中,哪些词嵌入更新最多。

变化最大的嵌入是什么?

我们可以采用初始 GloVe 嵌入和最终学习的嵌入,并通过对每个单词的差异进行归一化来比较它们。 然后,我们可以对标准值进行排序,以查看哪些词变化最大。 这是执行此操作的代码:

learned_embeddings = amazon_review_model.get_classification_model()
                           .get_layer('embedding').get_weights()[0]
embd_change = {}
for word, i in preprocessor.word_index.items(): 
    embd_change[word] = np.linalg.norm(initial_embeddings[i]- 
                                              learned_embeddings[i])
embd_change = sorted(embd_change.items(), key=lambda x: x[1],  
                     reverse=True)
embd_change[0:20]

您可以检查是否最新的嵌入是针对意见词的。

迁移学习 – 应用到 IMDB 数据集

我们应该使用迁移学习的一种情况是,手头任务的标签数据少得多,而相似但不同的领域的训练数据很多。 IMDB 数据集是二元情感分类数据集。 它拥有 25,000 条用于训练的电影评论和 25,000 条用于测试的电影评论。 关于此数据集,有很多已发表的论文,并且可能通过来自 Google 的 Mikolov 的段落向量在此数据集上获得最佳结果。 他们在此数据集上实现了 92.58% 的准确率。 SVM 达到了 89%。 这个数据集的大小不错,我们可以从头开始训练 CNN 模型。 这为我们提供了与 SVM 相当的结果。 下一节将对此进行讨论。

现在,让我们尝试使用少量的 IMDB 数据样本(例如 5%)构建模型。 在许多实际情况下,我们面临训练数据不足的问题。 我们无法使用此小型数据集训练 CNN。 因此,我们将使用迁移学习为该数据集构建模型。

我们首先按照与其他数据集相同的步骤预处理和准备数据:

train_df = Loader.load_imdb_data(directory = 'train')
train_df = train_df.sample(frac=0.05, random_state = train_params.seed) 
#take only 5%
print(train_df.shape)
test_df = Loader.load_imdb_data(directory = 'test')
print(test_df.shape)
corpus = train_df['review'].tolist()
target = train_df['sentiment'].tolist()
corpus, target = remove_empty_docs(corpus, target)
print(len(corpus))
preprocessor = Preprocess(corpus=corpus)
corpus_to_seq = preprocessor.fit()
test_corpus = test_df['review'].tolist()
test_target = test_df['sentiment'].tolist()
test_corpus, test_target = remove_empty_docs(test_corpus, test_target)
print(len(test_corpus))
test_corpus_to_seq = preprocessor.transform(test_corpus)
x_train = np.array(corpus_to_seq)
x_test = np.array(test_corpus_to_seq)
y_train = np.array(target)
y_test = np.array(test_target)
print(x_train.shape, y_train.shape)
glove=GloVe(50)
initial_embeddings = glove.get_embedding(preprocessor.word_index)
#IMDB MODEL

现在,让我们先加载训练后的模型。 我们有两种加载方法:模型的超参数和DocumentModel类中学习的模型权重:

def load_model(file_name): 
        with open(file_name, "r", encoding= "utf-8") as hp_file:
            model_params = json.load(hp_file)
            doc_model = DocumentModel( **model_params) 
            print(model_params)
        return doc_model 
def load_model_weights(self, model_weights_filename):
     self._model.load_weights(model_weights_filename, by_name=True)

然后,我们使用前述方法加载预训练的模型,然后按如下方法将学习到的权重转移到新模型中。 预训练模型的嵌入矩阵比语料库更大,单词更多。 因此,我们不能直接使用预训练模型中的嵌入矩阵。 我们将使用GloVe类中的update_embedding方法,使用经过训练的模型中的嵌入来更新 IMDB 模型的 GloVe 初始化的嵌入:

amazon_review_model = DocumentModel.load_model("model_file.json")
amazon_review_model.load_model_weights("model_weights.hdf5")
learned_embeddings = amazon_review_model.get_classification_model()\
                     .get_layer('embedding').get_weights()[0]
#update the GloVe embeddings.
glove.update_embeddings(preprocessor.word_index,  
                        np.array(learned_embeddings),    
                        amazon_review_model.word_index)

现在,我们都准备建立迁移学习模型。 让我们首先构建 IMDB 模型,然后从其他预训练模型初始化权重。 我们不会使用少量数据来训练该网络的较低层。 因此,我们将为其设置trainable=False。 我们将仅训练具有较大丢弃法率的最后一层:

initial_embeddings = glove.get_embedding(preprocessor.word_index)#get  
                                                   updated embeddings
imdb_model = DocumentModel(vocab_size=preprocessor.get_vocab_size(),
                           word_index = preprocessor.word_index,
                           num_sentences=Preprocess.NUM_SENTENCES, 
                           embedding_weights=initial_embeddings,
                           conv_activation = 'tanh',
                           train_embedding = False,
                           learn_word_conv = False,
                           learn_sent_conv = False,
                           hidden_dims=64, 
                           input_dropout=0.0, 
                                    hidden_layer_kernel_regularizer=0.001,
                                    final_layer_kernel_regularizer=0.01)
#transfer word & sentence conv filters
for l_name in ['word_conv','sentence_conv','hidden_0', 'final']:
    imdb_model.get_classification_model()\
              .get_layer(l_name).set_weights(weights=amazon_review_model
                                .get_classification_model()
                                .get_layer(l_name).get_weights())

在经过几个周期的训练之后,仅对隐藏层和最终的 S 型层进行了微调,我们在 25k 测试集上获得了 86% 测试精度。 如果我们尝试在这个小的数据集上训练 SVM 模型并预测整个 25k 测试集,则只能获得 82% 的准确率。 因此,即使我们的数据较少,迁移学习显然也有助于建立更好的模型。

使用 Word2vec 嵌入来训练完整 IMDB 数据集

现在,让我们尝试通过迁移学习到的 Word2vec 嵌入,在完整的 IMDB 数据集上训练文档 CNN 模型。

请注意,我们没有使用从 Amazon Review 模型中学到的权重。 我们将从头开始训练模型。 实际上,这就是本文所做的。

此代码与前面的 IMDB 训练代码非常相似。 您只需要从 Amazon 模型中排除权重加载部分。 该代码位于存储库中名为imdb_model.py的模块中。 另外,这是模型参数:

{  
    "embedding_dim":50,
    "train_embedding":true,
    "embedding_regularizer_l2":0.0,
    "sentence_len":30,
    "num_sentences":20,
    "word_kernel_size":5,
    "word_filters":30,
    "sent_kernel_size":5,
    "sent_filters":16,
    "sent_k_maxpool":3,
    "input_dropout":0.4,
    "doc_k_maxpool":5,
    "sent_dropout":0.2,
    "hidden_dims":64,
    "conv_activation":"relu",
    "hidden_activation":"relu",
    "hidden_dropout":0,
    "num_hidden_layers":1,
    "hidden_gaussian_noise_sd":0.3,
    "final_layer_kernel_regularizer":0.04,
    "hidden_layer_kernel_regularizer":0.0,
    "learn_word_conv":true,
    "learn_sent_conv":true,
    "num_units_final_layer":1
 }

训练时,我们使用了另一种技巧来避免过拟合。 我们在前 10 个时间段后冻结嵌入层(即train_embedding=False),仅训练其余层。 经过 50 个周期后,我们在 IMDB 数据集上实现了 89% 的准确率,这是本文提出的结果。 我们观察到,如果我们在训练之前不初始化嵌入权重,则模型将开始过拟合,并且无法实现 80% 以上的准确率验证。

使用 CNN 模型创建文档摘要

评论有很多句子。 这些句子中的一些是中性的,而某些则是多余的,无法确定整个文档的极性。 总结评论或在评论中突出显示用户实际表达意见的句子非常有用。 实际上,它也为我们所做的预测提供了解释,从而使模型可以解释。

如本文所述,文本摘要的第一步是通过为每个句子分配重要性分数来为文档创建显着性图。 为了生成给定文档的显着性图,我们可以应用以下技术:

  1. 我们首先通过网络执行前向传递,以生成文档的类别预测。
  2. 然后,我们通过反转网络预测来构造伪标签。
  3. 将伪标签作为真实标签输入到训练损失函数中。 伪标签的这种选择使我们能够造成最大的损失。 反过来,这将使反向传播修改对决定类标签贡献最大的句子嵌入的权重。 因此,在实际上是肯定标签的情况下,如果我们将 0 作为伪标签传递,则强阳性语句嵌入应该会看到最大的变化,即高梯度范数。
  4. 计算损失函数相对于句子嵌入层的导数。
  5. 按梯度范数按降序对句子进行排序,以使最重要的句子排在顶部。

让我们在 Keras 中实现它。 我们必须像以前一样进行预处理,并获得x_trainy_train NumPy 数组。 我们将首先加载训练有素的 IMDB 模型和学习的权重。 然后,我们还需要使用用于从该模型中获取导数和损失函数的优化器来编译模型:

imdb_model = DocumentModel.load_model(config.MODEL_DIR+ 
                                      '/imdb/model_02.json')
imdb_model.load_model_weights(config.MODEL_DIR+ '/imdb/model_02.hdf5')
model = imdb_model.get_classification_model()
model.compile(loss="binary_crossentropy", optimizer='rmsprop', 
              metrics=["accuracy"])

现在,让我们进行前面提到的步骤 1,即前向传递,然后生成伪标签:

preds = model.predict(x_train)
#invert predicted label
pseudo_label = np.subtract(1,preds)

为了计算梯度,我们将使用 Keras 函数model.optimizer.get_gradients()

#Get the learned sentence embeddings
sentence_ebd = imdb_model.get_sentence_model().predict(x_train)
input_tensors = [model.inputs[0], # input data
# how much to weight each sample by
                 model.sample_weights[0],
                 model.targets[0], # labels
                ]
#variable tensor at the sentence embedding layer
weights = imdb_model.get_sentence_model().outputs
#calculate gradient of the total model loss w.r.t 
#the variables at sentence embd layer 
gradients = model.optimizer.get_gradients(model.total_loss, weights) 
get_gradients = K.function(inputs=input_tensors, outputs=gradients)

现在,我们可以计算出一个文档(例如,文档编号10)的梯度,如下所示:

document_number = 10
K.set_learning_phase(0)
inputs = [[x_train[document_number]], # X
          [1], # sample weights
          [[pseudo_label[document_number][0]]], # y
]
grad = get_gradients(inputs)

现在,我们可以按梯度范数对句子进行排序。 我们将使用与预处理中使用的相同的nltk sent_tokenize函数来获取文本句子:

sent_score = []
for i in range(Preprocess.NUM_SENTENCES):
   sent_score.append((i, -np.linalg.norm(grad[0][0][i])))
sent_score.sort(key=lambda tup: tup[1])
summary_sentences = [ i for i, s in sent_score[:4]]
doc = corpus[document_number]
label = y_train[document_number]
prediction = preds[document_number]
print(doc, label , prediction)
sentences = sent_tokenize(doc)
for i in summary_sentences:
    print(i, sentences[i])

以下是否定的评论:

Wow, what a great cast! Julia Roberts, John Cusack, Christopher Walken, Catherine Zeta-Jones, Hank Azaria...what's that? A script, you say? Now you're just being greedy! Surely such a charismatic bunch of thespians will weave such fetching tapestries of cinematic wonder that a script will be unnecessary? You'd think so, but no. America's Sweethearts is one missed opportunity after another. It's like everyone involved woke up before each day's writing/shooting/editing and though "You know what? I've been working pretty hard lately, and this is guaranteed to be a hit with all these big names, right? I'm just gonna cruise along and let somebody else carry the can." So much potential, yet so painful to sit through. There isn't a single aspect of this thing that doesn't suck. Even Julia's fat suit is lame.

从前两个句子看来,这是非常积极的。 我们对该文件的预测得分是 0.15,这是正确的。 让我们看看我们得到了什么总结:

4 Surely such a charismatic bunch of thespians will weave such     
   fetching tapestries of cinematic wonder that a script will be    
   unnecessary?
 2 A script, you say?
 6 America's Sweethearts is one missed opportunity after another.

让我们再举一个积极的例子,这里我们的模型预测为 0.98:

This is what I was expecting when star trek DS9 premiered. Not to slight DS9\. That was a wonderful show in it's own right, however it never really gave the fans more of what they wanted. Enterprise is that show. While having a similarity to the original trek it differs enough to be original in it's own ways. It makes the ideas of exploration exciting to us again. And that was one of the primary ingredients that made the original so loved. Another ingredient to success was the relationships that evolved between the crew members. Viewers really cared deeply for the crew. Enterprise has much promise in this area as well. The chemistry between Bakula and Blalock seems very promising. While sexual tension in a show can often become a crutch, I feel the tensions on enterprise can lead to much more and say alot more than is typical. I think when we deal with such grand scale characters of different races or species even, we get some very interesting ideas and television. Also, we should note the performances, Blalock is very convincing as Vulcan T'pol and Bacula really has a whimsy and strength of character that delivers a great performance. The rest of the cast delivered good performances also. My only gripes are as follows. The theme. It's good it's different, but a little to light hearted for my liking. We need something a little more grand. Doesn't have to be orchestral. Maybe something with a little more electronic sound would suffice. And my one other complaint. They sell too many adds. They could fix this by selling less ads, or making all shows two parters. Otherwise we'll end up seeing the shows final act getting wrapped up way too quickly as was one of my complaints of Voyager.

这是摘要:

2 That was a wonderful show in it's own right, however it never really 
   gave the fans more of what they wanted.
 5 It makes the ideas of exploration exciting to us again.
 6 And that was one of the primary ingredients that made the original 
   so loved.
 8 Viewers really cared deeply for the crew.

您会看到它很好地掌握了摘要句子。 您真的不需要通过整个审查来理解它。 因此,此文本 CNN 模型可与 IMDB 数据集的最新模型相媲美,而且一旦学习,它就可以执行其他高级文本分析任务,例如文本摘要。

使用 CNN 模型进行多类分类

现在,我们将相同的模型应用于多类分类。 我们将为此使用 20 个新闻组数据集。 为了训练 CNN 模型,此数据集很小。 我们仍然会尝试解决一个更简单的问题。 如前所述,该数据集中的 20 个类有很多混合,使用 SVM,我们可以获得最高 70% 的准确率。 在这里,我们将采用该数据集的六大类,并尝试构建 CNN 分类器。 因此,首先我们将 20 个类别映射到 6 个大类别。 以下是首先从 scikit Learn 加载数据集的代码:

def load_20newsgroup_data(categories = None, subset='all'):
        data = fetch_20newsgroups(subset=subset,
                              shuffle=True,
                              remove=('headers', 'footers', 'quotes'),
                              categories = categories)
        return data
dataset = Loader.load_20newsgroup_data(subset='train')
corpus, labels = dataset.data, dataset.target
test_dataset = Loader.load_20newsgroup_data(subset='test')
test_corpus, test_labels = test_dataset.data, test_dataset.target

接下来,我们将 20 个类映射到六个类别,如下所示:

six_groups = {
    'comp.graphics':0,'comp.os.ms-  
       windows.misc':0,'comp.sys.ibm.pc.hardware':0,
    'comp.sys.mac.hardware':0, 'comp.windows.x':0,
    'rec.autos':1, 'rec.motorcycles':1, 'rec.sport.baseball':1,   
    'rec.sport.hockey':1,
    'sci.crypt':2, 'sci.electronics':2,'sci.med':2, 'sci.space':2,
    'misc.forsale':3,
    'talk.politics.misc':4, 'talk.politics.guns':4,    
    'talk.politics.mideast':4,
    'talk.religion.misc':5, 'alt.atheism':5, 'soc.religion.christian':5
    }
map_20_2_6 = [six_groups[dataset.target_names[i]] for i in range(20)]
labels = [six_groups[dataset.target_names[i]] for i in labels] 
test_labels = [six_groups[dataset.target_names[i]] for i in  
               test_labels]

我们将执行相同的预处理步骤,然后进行模型初始化。 同样,在这里,我们使用了 GloVe 嵌入来初始化单词嵌入向量。 详细代码在20newsgrp_model模块的存储库中。 这是模型的超参数:

{  
    "embedding_dim":50,
    "train_embedding":false,
    "embedding_regularizer_l2":0.0,
    "sentence_len":30,
    "num_sentences":10,
    "word_kernel_size":5,
    "word_filters":30,
    "sent_kernel_size":5,
    "sent_filters":20,
    "sent_k_maxpool":3,
    "input_dropout":0.2,
    "doc_k_maxpool":4,
    "sent_dropout":0.3,
    "hidden_dims":64,
    "conv_activation":"relu",
    "hidden_activation":"relu",
    "hidden_dropout":0,
    "num_hidden_layers":2,
    "hidden_gaussian_noise_sd":0.3,
    "final_layer_kernel_regularizer":0.01,
    "hidden_layer_kernel_regularizer":0.0,
    "learn_word_conv":true,
    "learn_sent_conv":true,
    "num_units_final_layer":6
 }

这是测试集上模型的详细结果:

precision recall f1-score support
 0                  0.80   0.91     0.85    1912
 1                  0.86   0.85     0.86    1534
 2                  0.75   0.79     0.77    1523
 3                  0.88   0.34     0.49     382
 4                  0.78   0.76     0.77    1027
 5                  0.84   0.79     0.82     940
 avg / total        0.81   0.80     0.80    7318
 [[1733    41   114    1   14    9]
  [  49  1302   110   11   47   15]
  [ 159    63  1196    5   75   25]
  [ 198    21    23  130    9    1]
  [  10    53    94    0  782   88]
  [  22    30    61    0   81  746]]
 0.8047280677780815

Python 迁移学习实用指南:6~11(3)https://developer.aliyun.com/article/1426855

相关文章
|
13天前
|
数据处理 索引 Python
【Python学习篇】Python实验小练习——文件操作(十一)
【Python学习篇】Python实验小练习——文件操作(十一)
28 1
|
13天前
|
存储 缓存 算法
【Python学习篇】Python实验小练习——循环结构(八)
【Python学习篇】Python实验小练习——循环结构(八)
15 1
|
13天前
|
存储 算法 数据安全/隐私保护
【Python学习篇】Python实验小练习——高级数据结构(五)
【Python学习篇】Python实验小练习——高级数据结构(五)
29 1
|
13天前
|
存储 Python
【Python学习篇】Python判断语句(四)
【Python学习篇】Python判断语句(四)
17 1
|
13天前
|
人工智能 IDE 开发工具
【Python学习篇】Python基础入门学习——你好Python(一)
【Python学习篇】Python基础入门学习——你好Python(一)
19 1
|
29天前
|
C语言 Python
​python学习之变量类型​
​python学习之变量类型​
|
3天前
|
Python
GitHub爆赞!终于有大佬把《Python学习手册》学习笔记分享出来了
这份笔记的目标是为了给出一份比较精炼,但是又要浅显易懂的Python教程。《Python学习手册》中文第四版虽然比较简单,但是措辞比较罗嗦,而且一个语法点往往散落在多个章节,不方便读者总结。 我在做笔记时,将一个知识点的内容都统筹在一个章节里面,因此提炼性大大提高。而且还有《Python学习手册》中文第四版的翻译在某些章节(可能难度较大?)措辞可能前后矛盾。当知识点提炼之后就能够很快的找到一些难以理解的概念的上下文,方便吃透这些难点。
|
24天前
|
存储 索引 Python
python学习——NumPy数值计算基础
NumPy基础知识概览:涉及nan(非数字)和inf(无穷)的概念,nan在文件读取或不适当计算时出现,inf在除0操作中出现。数组操作有深拷贝(a=b.copy())、浅拷贝(a=b[:])和引用(a=b)。创建数组方式多样,如`np.array()`、`np.arange()`等。数据类型转换如`np.float64()`、`np.int8()`。随机数生成包含均匀分布、正态分布等。数组索引和切片支持多维操作。改变数组形状用`reshape()`,展平用`ravel()`和`flatten()`。矩阵运算包括加减乘、转置、逆矩阵等。
40 2
python学习——NumPy数值计算基础
GitHub爆赞!终于有大佬把《Python学习手册》学习笔记分享出来了
这份笔记的目标是为了给出一份比较精炼,但是又要浅显易懂的Python教程。《Python学习手册》中文第四版虽然比较简单,但是措辞比较罗嗦,而且一个语法点往往散落在多个章节,不方便读者总结。 我在做笔记时,将一个知识点的内容都统筹在一个章节里面,因此提炼性大大提高。而且还有《Python学习手册》中文第四版的翻译在某些章节(可能难度较大?)措辞可能前后矛盾。当知识点提炼之后就能够很快的找到一些难以理解的概念的上下文,方便吃透这些难点。
|
11天前
|
机器学习/深度学习 人工智能 数据挖掘
从零开始学习Python编程语言
本文将介绍Python编程语言的基础知识和学习路径,帮助初学者快速入门,并为进阶学习提供指引。首先,我们将讨论Python的特点和应用领域,然后深入探讨Python的基础语法、常用数据类型和控制流程,最后介绍如何利用Python进行简单的编程实践。