论文Multi-Perspective Sentence Similarity Modeling with Convolution Neural Networks实现之数据集制作

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_34613450/article/details/82350456

1.数据集

本文采用的是STS数据集,如下图所示,包括所有的2012-2016年的数据,而all文件夹包含2012-2015的所有数据。

每一个文件的具体数据如下所示,每一行为一个三元组:<相似性得分,句子1,句子2>.

在实现时将all文件夹中的所有数据当作训练集,将2016年的文件当作测试集。

1.1数据读取

采用以下代码进行单个文件的数据读取:

"""读取一个数据集文件"""
def load_one_sts(filename):
    s0s = []
    s1s = []
    labels = []
    num_samples = 0
    with open(filename, 'r', encoding='utf-8') as f:
        for line in f:
            #rstrip:是从字符串最右边删除了参数指定字符后的字符串,不带参数进去则是去除最右边的空格
            #strip:同时去除左右两边指定的字符,不带参数进去则是去除空格
            data = line.rstrip()
            # line = data.split('\t')
            # print(line)
            label, s0, s1 = data.split('\t')
            #如果没有对应的相似性得分,则直接跳过
            if label == '':
                continue
            else:
                score = round(float(label)) #如果距离两边一样远,会保留到偶数的一边。比如round(0.5)和round(-0.5)都会保留到0,而round(1.5)会保留到2
                # scores.append(score)
                """经验证可知score的取值范围为0-5,故标签使用one-hot encoding,数目为6"""
                y = [0] * 6  #此时的y是一个list
                y[score] = 1 #将score值所对应的位置置为1
                labels.append(np.array(y)) #此时label转换完成,内置元素应为array
                # labels = np.asarray(labels)
                num_samples = len(labels)
                s0s.append(s0)
                s1s.append(s1)

注意:如上面所示代码data = line.rstrip(),在本地文件中,有的两个句子是没有对应的相似度得分的,此时对应字段为空,如果使用data = line.strip(),程序会将这一行左面的空格去掉,在后面进行循环读取每个文件的时候会报错:You don't get unenough unpacks(expected 3, get 2)这样的信息。

此时已经将相似度得分,句子1,句子2分别存储,接下来就是将每一个句子映射成id索引的组合形式,这就需要读入GloVe模型,以下代码为读入GloVe模型的辅助函数(这些函数在另一个文件embedding.py中):

import word2vec
import os
import shutil
from sys import platform
import numpy as np
import pandas as pd

# 计算行数,就是单词数
def getFileLineNums(filename):
    f = open(filename, 'r', encoding='utf-8')
    count = 0
    for line in f:
        count += 1
    return count

# Linux或者Windows下打开词向量文件,在开始增加一行
def prepend_line(infile, outfile, line):
    with open(infile, 'r', encoding='utf-8') as old:
        with open(outfile, 'w', encoding='utf-8') as new:
            new.write(str(line) + "\n")
            shutil.copyfileobj(old, new)

def prepend_slow(infile, outfile, line):
    with open(infile, 'r', encoding='utf-8') as fin:
        with open(outfile, 'w', encoding='utf-8') as fout:
            fout.write(line + "\n")
            for line in fin:
                fout.write(line)

"""生成符合word2vec工具读取格式的模型文件"""
def normalize_data(filename):
    num_lines = getFileLineNums(filename)
    model_file = 'glove_model_50d.txt'
    model_first_line = "{} {}".format(num_lines, 50)
    # Prepends the line.
    if platform == "linux" or platform == "linux2":
        prepend_line(filename, model_file, model_first_line)
    else:
        prepend_slow(filename, model_file, model_first_line)
    print('模型向量文件数据已规范化!后续请使用文件', model_file)

"""读取Glove模型,生成id和词向量"""
def load_glove_model(glove_model_path):
    normalize_data(glove_model_path)
    wv = word2vec.load('glove_model_50d.txt')
    print('GloVe模型载入完毕!')
    vocab = wv.vocab
    word2id = pd.Series(range(1, len(vocab)+1), index=vocab)
    #将未知词对应的id设置为0,对应word_embedding中的第0行
    word2id['<unk>'] = 0
    # print(word2id[399990:])
    print('word2id转换完成,未知词使用<unk>标识符!')
    word_embedding = wv.vectors
    #采取均值作为未知词的词向量表示
    word_mean = np.mean(word_embedding, axis=0)
    word_embedding = np.vstack([word_mean, word_embedding])
    # print(word_embedding[:2])
    print('id词向量嵌入完成!')

    return word2id, word_embedding

由于官方提供的GloVe文件格式并不符合word2vec工具读取的要求,故使用其中的normalize_data()将其标准化,未知词采用‘<unk>’标记,继而调用load_glove_model()得到word2id和word_embedding。得到词对应的嵌入向量之后,对第一步读取到的数据进行映射。

"""通过单词获取id"""
def get_id(word):
    if word in word2id:
        return word2id[word]
    else:
        return word2id['<unk>']

"""数据清洗并将句子表示成索引组合"""
def seq2id(texts):
    texts = clean_text(texts)
    texts = texts.split(' ')
    texts_id = map(get_id, texts)
    return texts_id

"""填充句子, padding_length:句子填充的长度"""
def padding_sentence(s0, s1, padding_length):
    sentence_num = len(s1)
    # sentence_length = 100
    # print('句子填充长度为100')
    s0s = []
    s1s = []

    for s in s0:
        left = padding_length -len(s)
        pad = [0] * left
        s= list(s)
        s.extend(pad)
        s0s.append(np.array(s))
    for s in s1:
        left = padding_length -len(s)
        pad = [0] * left
        s= list(s)
        s.extend(pad)
        s1s.append(np.array(s))

    # print('%d个句子填充完毕!'%sentence_num)
    return s0s, s1s

上面所示为句子映射的辅助函数,其中seqid()用来得到句子各个单词对应的id。实际情况中每条句子的长度都不一样,导致输入网络的tensor长度也不一致,故此处调用padding_sentence()填充各个句子(此处使用定长100, 有些地方使用最长句子的长度来进行填充)。

其中数据清洗函数如下所示,对于其中的标点(!、......等)、缩写(You're替换成You 're)等进行处理:

"""数据清洗函数"""
def clean_text(line):
    # print('过滤前--------------->', line)
    #替换掉无意义的单个字符
    line = re.sub(r'[^A-Za-z0-9(),!?.\'\`]', ' ', line)
    """使用空格将单词后缀单独分离开来"""
    line = re.sub(r'\'s', ' \'s ', line)
    line = re.sub(r'\'ve', ' \'ve ', line)
    line = re.sub(r'n\'t', ' n\'t ', line)
    line = re.sub(r'\'re', ' \'re ', line)
    line = re.sub(r'\'d', ' \'d ',line)
    line = re.sub(r'\'ll', ' \'ll ',line)
    """使用空格将标点符号、括号等字符单独分离开来"""
    line = re.sub(r',', ' , ', line)
    line = re.sub(r'!', ' ! ', line)
    line = re.sub(r'\?', ' \? ', line)
    line = re.sub(r'\(', ' ( ', line)
    line = re.sub(r'\)', ' ) ', line)
    line = re.sub(r'\s{2,}', ' ', line)
    # line = re.sub(r'\n', '', line)
    # line = re.sub(r'')
    # line = re.sub(r',', ' , ', line)
    # print('过滤后--------------->',line)
    return line.strip().lower()

做完上述动作,继续在load_one_sts()函数中进行编辑:

def load_one_sts(filename):
    """以下是紧接着第一部分继续编写的代码,二者合起来才是一个完整的函数"""
    s0s_id = []
    for s0 in s0s:
        s0_id = list(seq2id(s0))
        # print(type(s0_id))
        # print('s0_id:', s0_id)
        s0s_id.append(np.asarray(s0_id))

    s1s_id = []
    for s1 in s1s:
        s1_id = list(seq2id(s1))
        s1s_id.append(np.asarray(s1_id))

    #句子填充,填充长度为100
    s0_padding, s1_padding= padding_sentence(s0s_id, s1s_id, 100)
    # print(len(s0_padding[0]))
    # print(s0_padding[0])
    return s0_padding, s1_padding, labels

如上所示,单个文件的读取编写完毕,接下来需要遍历某个路径下的所有文件,将所得到的数据放入s0,s1,labels中。

"""将不同文件的数据进行拼接"""
def concat(data):
    s0s = []
    s1s = []
    labels = []
    for s0, s1, label in data:
        s0s += s0
        s1s += s1
        labels += label
    s0s = np.asarray(s0s)
    s1s = np.asarray(s1s)
    labels = np.asarray(labels)
    return s0s, s1s, labels


"""读取整个数据集"""
def load_datasets(path):
    files = []
    #列出路径path下所有的文件
    for dirpath,dirnames,filenames in os.walk(path):
        for filename in filenames:
            # print(os.path.join(dirpath,filename))
            files.append(dirpath + '/' + filename)

    s0, s1, labels = concat([load_one_sts(file) for file in files])

    return ([s0, s1], labels)

这样,我们就完成了某个路径下的所有数据文件的读取工作。

为了放心,对其进行测试,读取测试集和训练集:

print('读取训练集-------》')
path = './sts/semeval-sts/all'
x_train, y_train = load_datasets(path)
print('训练集样本数:', len(y_train))

print('读取测试集-------》')
path = './sts/semeval-sts/2016'
x_test, y_test = load_datasets(path)
print('测试集样本数:', len(y_test))

程序执行结果如下所示:

OK,至此我们就完成了数据集的读取和创建。下一步就是创建神经网络模型MPCNN,并应用其中的相似度计算公式来得到相似度得分。

猜你喜欢

转载自blog.csdn.net/weixin_34613450/article/details/82350456