感受大佬的代碼寫的就是好,在處理數據的方面,首先定義一個 提取特徵的類, 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