分析 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
相關文章
相關標籤/搜索