感受大佬的代码写的就是好,在处理数据的方面,首先定义一个 提取特征的类, 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
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]
这个方法是统计单词的权重及比例,函数
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
这里主要考虑的是解析树的构成,构建一颗解析树,从语句的解析树提取句子的特征,编码
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