分析 Kaggle TOP0.1% 如何处理文本数据

感受大佬的代码写的就是好,在处理数据的方面,首先定义一个 提取特征的类, class Extractor(object):,而后每一种方法对这个类进行重构,这个类主要结构就是:python

class Extractor(object):
    def __init__(self, config_fp):
        # set feature name
        self.feature_name = self.__class__.__name__
        # set feature file path
        self.data_feature_fp = None
        # load configuration file
        self.config = configparser.ConfigParser()
        self.config.read(config_fp)

    # 这个函数什么都没写,就是用来重构的,表示特征的个数
    def get_feature_num(self):
        assert False, 'Please override function: Extractor.get_feature_num()'
        # assert False 后面加字符串就是为了解释哪里出错的,给出错误信息


    def extract_row(self, row):
        assert False, 'Please override function: Extractor.extract_row()'

    # 抽取原始数据的特征
    def extract(self, data_set_name, part_num=1, part_id=0):
        """
        Extract the feature from original data set
        :param data_set_name: name of data set
        :param part_num: number of partitions of data
        :param part_id: partition ID which will be extracted
        :return:
        """

接下来看如何具体的从统计的角度与 NLP 的角度处理数据json

统计学的角度处理数据的方法

从统计学的角度考虑主要是单词的频率,数据的次数等等,这里考虑的问题不少,总结来讲就是,每种方法的处理套路是,使用分词,而后使用词干提取的方法,将全部的单词进行词干归一化处理。注意,这里是从统计的角度考虑问题,那么就不要考虑到单词的时态问题,词干提取能够更好的表示出现的频率。数据结构

主要使用的方法总结,以后我会具体说一下使用的比较复杂的方法:app

  1. Not 类,统计一句话中 ‘not’ 出现的次数
  2. WordMatchShare 类,两句话中均出现的单词的占全部单词的比例
  3. TFIDFWordMatchShare 类,与前面的相似,只不过这里加权了文件的出现次数
  4. Length 类,表示长度的一些数据
  5. LengthDiff 类表示问题的长度之差
  6. LengthDiffRate 类,数据上比较长短
  7. PowerfulWord 这个类是为后面服务的,计算数据中词语的影响力
  8. PowerfulWordOneSide 类,考虑单词出现的比例以及正确的比例
  9. PowerfulWordDoubleSideRate 类,考虑两边都出现的单词的比例,以及这些单词对应的 label 的比例
  10. TFIDF 类,使用 sklearn 中方法直接获取 TFIDF 类
  11. DulNum 类,计算彻底重复的问题的数量
  12. EnCharCount 类,统计每句话中字母的出现的频率,
  13. NgramJaccardCoef 类,使用 ngram 的方法计算两个问题之间的距离
  14. NgramDiceDistance 类,与上面的方法相似,只是计算距离的方法不一样
  15. Distance 类,这里是下面的方法父类,是用于计算句子之间距离的工具
  16. NgramDistance 类,这个主要是在上面的基础上,结合矩阵的距离的方法
  17. InterrogativeWords 类,这个主要是统计疑问句的比例

TFIDFWordMatchShare 方法

TFIDFWordMatchShare 这个方法是考虑单词出如今文件的次数,也就是 IDF 的意思,而后用这个加权来表示共同出现的单词的加权的文件的比例,这里具体看下重点的代码就是:ide

def init_idf(data):
    idf = {}
    # 先统计了单词的 IDF 值
    num_docs = len(data)
    for word in idf:
        idf[word] = math.log(num_docs / (idf[word] + 1.)) / math.log(2.)
        # 这里是一个对数中的换底公式
    LogUtil.log("INFO", "idf calculation done, len(idf)=%d" % len(idf))
    # 返回一个字典,是整个文件中的单词的,平均每一个单词出如今文件中的次数,
    # 好比说,5个文件,这个单词一共出现了三次,那么就是 5/3
    return idf

def extract_row(self, row):
    qwords = {}
    # 这里是先统计了单词出现的次数, qword来计算,
    # 下面的这个公式计算的是, 同时出如今两个问题中的单词他们所加权的文件总数
    # 好比说,上面前面计算 IDF 是对于整个文件来讲,单词 'word'的idf值是 5/3,那么对于这一句话来讲,'word' 出现了两次,而且       # 'word' 在两个问题均出现,那么这个值就是 10 /3 ,而后对于每一个出现的单词计算就能够了
    sum_shared_word_in_q1 = sum([q1words[w] * self.idf.get(w, 0) for w in q1words if w in q2words])
    sum_shared_word_in_q2 = sum([q2words[w] * self.idf.get(w, 0) for w in q2words if w in q1words])
    sum_tol = sum(q1words[w] * self.idf.get(w, 0) for w in q1words) + sum(
        q2words[w] * self.idf.get(w, 0) for w in q2words)
    if 1e-6 > sum_tol:
        return [0.]
    else:
        return [1.0 * (sum_shared_word_in_q1 + sum_shared_word_in_q2) / sum_tol]

PowerfulWord 方法

这个方法是统计单词的权重及比例,函数

def generate_powerful_word(data, subset_indexs):
    """
        计算数据中词语的影响力,格式以下:
           词语 --> [0. 出现语句对数量,1. 出现语句对比例,2. 正确语句对比例,3. 单侧语句对比例,4. 单侧语句对正确比例,
           5. 双侧语句对比例,6. 双侧语句对正确比例]
        """
        words_power = {}
        train_subset_data = data.iloc[subset_indexs, :]
        # 取出 subset_indexs中的全部的行
        # 而后遍历 subset_indexs 中的全部的行
        
class PowerfulWordDoubleSide(Extractor):
        # 经过设置阈值来提取这句话中关键的单词,而后组成单词向量
        def init_powerful_word_dside(pword, thresh_num, thresh_rate):
        pword_dside = []
        # 出现语句对的数量乘以双侧语句对的比例,获得双侧语句对的数量,
        # 计算两边都出现,而且比例高的单词,而后从大到小排序
        pword = filter(lambda x: x[1][0] * x[1][5] >= thresh_num, pword)
        # 抽取出 pword 中知足条件的全部项目
        pword_sort = sorted(pword, key=lambda d: d[1][6], reverse=True)
        # 表示按照降序排序
        pword_dside.extend(map(lambda x: x[0], filter(lambda x: x[1][6] >= thresh_rate, pword_sort)))
        # 在list的结尾追加一序列的值
        LogUtil.log('INFO', 'Double side power words(%d): %s' % (len(pword_dside), str(pword_dside)))
        return pword_dside

句子之间的距离的计算

因为以前本身对句子之间的距离的了解也比较少,因此这里写的详细一点,这里的主要思想是工具

class Distance(Extractor):
    
    def __init__(self, config_fp, distance_mode):
        Extractor.__init__(self, config_fp)
        self.feature_name += '_%s' % distance_mode
        self.valid_distance_mode = ['edit_dist', 'compression_dist']
        # 这两个方法是计算两个字符串之间的距离使用的,其中 'edit_dist' 直接调用的一个方法,而'compression_dist'经过简单计算
        # 就能够获得
        assert distance_mode in self.valid_distance_mode, "Wrong aggregation_mode: %s" % distance_mode
        # 初始化参数
        self.distance_mode = distance_mode
        # 使用不一样的方法来计算字符串之间的距离
        self.distance_func = getattr(DistanceUtil, self.distance_mode)

    def extract_row(self, row):
        q1 = str(row['question1']).strip()
        q2 = str(row['question2']).strip()
        q1_stem = ' '.join([snowball_stemmer.stem(word).encode('utf-8') for word in
                      nltk.word_tokenize(TextPreProcessor.clean_text(str(row['question1']).decode('utf-8')))])
        q2_stem = ' '.join([snowball_stemmer.stem(word).encode('utf-8') for word in
                      nltk.word_tokenize(TextPreProcessor.clean_text(str(row['question2']).decode('utf-8')))])
        # 先分词,而后将单词用空格连成句子,伪装是句子
        return [self.distance_func(q1, q2), self.distance_func(q1_stem, q2_stem)]

    def get_feature_num(self):
        return 2
    
class NgramDistance(Distance):
    # 这里没有构造函数,子类直接调用父类的构造函数
    def extract_row(self, row):
        # 使用词干提取
        fs = list()
        aggregation_modes_outer = ["mean", "max", "min", "median"]
        aggregation_modes_inner = ["mean", "std", "max", "min", "median"]
        # 这些主要是 np 中矩阵的一些方法,用于数据处理均值,最大值,最小值,中位数,矩阵的标准差 等等
        for n_ngram in range(1, 4):
            q1_ngrams = NgramUtil.ngrams(q1_words, n_ngram)
            q2_ngrams = NgramUtil.ngrams(q2_words, n_ngram)

            val_list = list()
            for w1 in q1_ngrams:
                _val_list = list()
                for w2 in q2_ngrams:
                    s = self.distance_func(w1, w2)
                    # 两个句子在 ngram 下面的距离,而后存起来
                    _val_list.append(s)
                if len(_val_list) == 0:
                    _val_list = [MISSING_VALUE_NUMERIC]
                val_list.append(_val_list)
            if len(val_list) == 0:
                val_list = [[MISSING_VALUE_NUMERIC]]
            # val_list 存的就是在 q1_ngrams 下每两个句子之间的距离,组成一个矩阵
            for mode_inner in aggregation_modes_inner:
                tmp = list()
                for l in val_list:
                    tmp.append(MathUtil.aggregate(l, mode_inner))
                    fs.extend(MathUtil.aggregate(tmp, aggregation_modes_outer))
            return fs

    def get_feature_num(self):
        return 4 * 5

NLP 角度处理数据的方法

这里主要考虑的是解析树的构成,构建一颗解析树,从语句的解析树提取句子的特征,编码

def init_tree_properties(tree_fp):
    features = {}
    f = open(tree_fp)
    for line in f:
        [qid, json_s] = line.split(' ', 1)
        # 分割一次,分红两个
        features[qid] = []
        root = -1
        parent = {}
        indegree = {}
        # calculate in-degree and find father
        # 删除开头与结尾的空格
        if 0 < len(json_s.strip()):
            tree_obj = json.loads(json_s)
            # 将一个JSON编码的字符串转换回一个Python数据结构:
            # 返回一个字典
            assert len(tree_obj) <= 1
            tree_obj = tree_obj[0]
            for k, r in sorted(tree_obj.items(), key=lambda x: int(x[0]))[1:]:
                if r['word'] is None:
                    continue
                head = int(r['head'])
                k = int(k)
                if 0 == head:
                    root = k
                parent[k] = head
                indegree[head] = indegree.get(head, 0) + 1
        # calculate number of leaves
        n_child = 0
        for i in parent:
            if i not in indegree:
                n_child += 1
        # calculate the depth of a tree
        depth = 0
        for i in parent:
            if i not in indegree:
                temp_id = i
                temp_depth = 0
                while (temp_id in parent) and (0 != parent[temp_id]):
                    temp_depth += 1
                    temp_id = parent[temp_id]
                depth = max(depth, temp_depth)
        # calculate the in-degree of root
        n_root_braches = indegree.get(root, 0)
        # calculate the max in-degree
        n_max_braches = 0
        if 0 < len(indegree):
            n_max_braches = max(indegree.values())
        features[str(qid)] = [n_child, depth, n_root_braches, n_max_braches]
    f.close()
    return features
相关文章
相关标签/搜索