機器學習建模全流程及資料總結(2)

書接上文。機器學習建模全流程及資料總結——以文本分類風控建模實驗爲例(1)

4. 特徵篩選

前述特徵工程的工作從原始數據中提取,構造了衆多特徵,一般來說是可以直接用於模型訓練的。

但是對於機器學習模型來說,並不是特徵越多越好。特徵中冗餘的信息,與目標值無關的變量,多個特徵之間的線性關係等,都會對模型訓練產生負面影響。

因此進行特徵對篩選是很必要對步驟。我們可以隨意的構造衆多特徵,在篩選的時候根據數據量,問題場景等,或者經驗,篩選出一部分特徵,再傳入模型訓練。

特徵等篩選又是很複雜的一件事情了。根據特徵選擇的形式又可以將特徵選擇方法分爲3種:

  • Filter過濾法:按照發散性或者相關性對各個特徵進行評分,設定閾值或者待選擇閾值的 個數,選擇特徵。
  • Wrapper包裝法:根據目標函數(通常是預測效果評分),每次選擇若干特徵,或者排除若干特徵。
  • Embedded嵌入法:先使用某些機器學習的算法和模型進行訓練,得到各個特徵的權值 係數,根據係數從大到小選擇特徵。類似於Filter方法,但是是通過訓練來確定特徵的優劣。

主要用到的東西參考 https://sklearn.apachecn.org/docs/master/14.html

# 在 sklearn.feature_selection 模塊中的類可以用來對樣本集進行 feature selection(特徵選擇)和 dimensionality reduction(降維)
# 1. 方差選擇法
from sklearn.feature_selection import VarianceThreshold
sel = VarianceThreshold(threshold=0.1)

# 2. 單變量特徵選擇
# 篩選框架常用以下2個方法:
# SelectKBest 移除那些除了評分最高的 K 個特徵之外的所有特徵
# SelectPercentile 移除除了用戶指定的最高得分百分比之外的所有特徵

# 這兩個方法需要接收一個評分函數,常用的包括:
# 對於迴歸: f_regression , mutual_info_regression
# 對於分類: chi2 , f_classif , mutual_info_classif

# 使用方法也很簡單
from sklearn.feature_selection import SelectKBest, chi2
features_selector_chi = SelectKBest(chi2, 20)
model_chi = features_selector_chi.fit(train_x, y)
best_features = model_chi.get_support(indices=True) # 我們就得到了篩選出來的特徵的索引

# 3. 嵌入法特徵選擇
“”“
SelectFromModel 是一個 meta-transformer(元轉換器) ,它可以用來處理任何帶有 coef_ 或者 feature_importances_ 屬性的訓練之後的評估器。 如果相關的coef_ 或者 featureimportances 屬性值低於預先設置的閾值,這些特徵將會被認爲不重要並且移除掉。
包括基於L1約束的特徵選擇和基於樹模型的特徵選擇,用法很簡單。
”“”
from sklearn.svm import LinearSVC
from sklearn.feature_selection import SelectFromModel
lsvc = LinearSVC(C=0.1, penalty="l1", dual=False).fit(X, y)
model = SelectFromModel(lsvc, prefit=True)

# 4. 遞歸式特徵消除
# 給定一個外部的估計器,可以對特徵賦予一定的權重(比如,線性模型的相關係數),recursive feature elimination ( RFE ) 通過考慮越來越小的特徵集合來遞歸的選擇特徵。
from sklearn.svm import SVC
from sklearn.feature_selection import RFE
svc = SVC(kernel="linear", C=1)
rfe = RFE(estimator=svc, n_features_to_select=1, step=1)
rfe.fit(X, y)

前三種方法嚴格對應於特徵選擇的三類,遞歸式的原理似乎不太相同,是逐漸消除差的特徵留下好的。

常用的還是前三種,方差選擇圖森破,只能簡單篩選,一般用包裝法及嵌入法。熟練使用特徵選擇,留下好特徵,下游模型訓練才更好呀。

 

三.  數據存儲

在前面漫長的過程後,得到的一組特徵矩陣X 和目標值Y,可以進行後面的模型訓練和實驗了。

在大多數的情況下,此時我們應該把這樣的半成品數據存下來,以便可以拿來即用,重複使用。

  • 將原始數據清洗過後,存儲下來
  • 將特徵提取/文本分詞處理後的數據存儲下來
  • 將X,Y矩陣存儲下來(這是可以直接訓練模型的數據)

按以上的思路,數據文件也會越來越小,丟失的信息回越來越多。

如何存儲也是很重要的。

  • 比較原始的數據,可以每個樣本json字符 存儲,每個樣本一行作爲一個對象(比如字典類),然後line = json.dumps()寫入文件
  • 略處理過的樣本,可以按行存儲,空格或"\t"分割, 如 1\t0.1\t0.5\t2\n,默認第一列是label
  • X,Y矩陣存儲就隨意多了,最好還是按行

要重視中間關鍵結果的存儲。一些分詞,數據處理的過程十分耗時,在進行多次實驗的情況下,沒必要每次處理。


下面我們把前面這些準備工作的流程,形成一份規範的格式和代碼。

# 1. 讀取原始數據,存儲爲ID與原始文本的映射
sms_data = dict()
with open("sms_data.txt", "r") as fr:
    for line in fr:
        uid, content = line.strip().split("\t")
        cont = json.loads(content)
        sms_data[uid] = [sms["body"] for sms in cont]

# 2. 讀取其他數據源的數據及label
label_dict = dict()
# 分類問題看一下 
sum(label_dict.values()) / len(label_dict)
# 迴歸問題看一下分佈

# 3. 存儲1 稍加處理整合的原始數據
# 原始數據 “uid1”: ["第一條短信", "第二條短信", "第三條短信"]
# “uid1” : 0
# 後面的文本分類,將一個用戶的所有短信合爲一個文檔,提取特徵,因此分詞及合併短信
fw = open("sample_sms_json", "w")
for uid, label in label_dict.items():
    text = sms_data.get(uid, None)
    data = dict()
    if text:
        data["id"] = uid
        data["label"] = int(label)
        data["其他信息"] = ""
        tokens = []
        for s in text:
            tokens.extend([st for st in word_tokenize(s) if 1<len(st)<20])  # 對短信分詞 篩選
        text_all = " ".join(tokens)
        data["text"] = text_all
        fw.write(json.dumps(data))
        fw.write("\n")
fw.close()

# 4. 預處理所有文本
# data_x 爲所有文本
pat_dig0 = re.compile(" \d{2,3} ")
pat_dig1 = re.compile(" \d{4,6} ")
pat_dig2 = re.compile(" \d{7,15} ")
pat_date = re.compile(" ((\d{2}|\d{4})[-/]\d?\d[-/](\d{2}|\d{4}))|((\d{2}|\d{4})[-/](jan|feb|mar|apr)[-/](\d{2}|\d{4})) ")
pat_time = re.compile("(\d\d:\d\d(:\d\d)?)")
all_words_list_for_nltk = []
data_x_list = []
# 文本應該進行的一些處理
for s in data_x:
    s = s.lower()
    s = pat_dig0.sub(" bi_digit ", s)
    s = pat_dig1.sub(" short_digit ", s)
    s = pat_dig2.sub(" long_digit ", s)
    s = pat_date.sub(" date_form ", s)
    s = pat_time.sub(" time_form ", s)
    
    words = s.split(" ")
    words = [w for w in words if len(w) > 1]
    data_x_list.append(words)
    all_words_list_for_nltk.extend(words)
# data_x_list 爲預處理後的每條樣本的文本
# all_words_list_for_nltk 爲彙總所有的單詞

# 5. 高頻詞 一元二元
unstop = [wd for wd in all_words_list_for_nltk if wd not in stopwds]
tflist = nltk.FreqDist(unstop)
finder = BigramCollocationFinder.from_words(unstop)
bi_tflist = finder.ngram_fd
# 存儲一份高頻詞 30000詞
with open("high_freq_word_", "w") as fw:
    cn = 0
    for wd, tf in tflist.most_common(15000):
        fw.write("%d\t%s\n" % (cn, wd))
        cn += 1
    for wd, tf in finder.ngram_fd.most_common(15000):
        s = "%s %s" % (wd[0], wd[1])
        fw.write("%d\t%s\n" % (cn, s))
        cn += 1
wd_map = dict()
id_map = dict()
with open("high_freq_word", "r") as fr:
    for line in fr:
        ix, wd = line.strip().split("\t")
        wd_map[wd] = int(ix)
        id_map[int(ix)] = wd

# 6. 先做訓練集 data_x_list 每一條一行特徵
train_x = np.zeros([len(data_x_list), 30000])
for i in range(len(data_x_list)):
    sample = data_x_list[i]
    # 找單個詞
    word_tf = nltk.FreqDist(sample)
    for wd, tf in word_tf.most_common():
        ix = wd_map.get(wd, -1)
        if ix >= 0:
            train_x[i, ix] = 1
    # 找二元詞
    finder = BigramCollocationFinder.from_words(sample)
    bigram_set = set()
    for wd, tf in finder.ngram_fd.most_common():
        bigram_set.add("%s %s" % (wd[0], wd[1]))
    for ele in bigram_set:
        ix = wd_map.get(ele, -1)
        if ix >= 0:
            train_x[i, ix] = 1
# 豎項相加 爲idf值
idf_list = np.sum(train_x, axis=0)

# 7.卡方檢驗篩選3000特徵詞
from sklearn.feature_selection import SelectKBest, chi2, mutual_info_classif
features_selector_chi = SelectKBest(chi2, 3000)
model_chi_3000 = features_selector_chi.fit(train_x, y)
best_features = model_chi_3000.get_support(indices=True)
# 計算idf 並且篩一下
feature_map = dict()
idf_map = dict()
with open("feature_word_v2_3000l1", "w") as fw:
    for ix in best_features:
        feature_map[cn] = id_map[ix]
        idf = idf_list[ix]
        if idf < 10:
            continue
        idf = math.log(len(data_x_list)/(idf+1))
        idf_map[id_map[ix]] = idf
        fw.write("%d\t%s\t%.4f\n" % (cn, id_map[ix], idf))

# 8. 生成特徵 存儲
train_x = np.zeros([len(data_x_list), cn])
for i in range(len(data_x_list)):
    sample = data_x_list[i]
    # 單個詞
    tf_dict = dict()
    wd_counter = Counter()
    wd_counter.update(sample)
    total_1 = len(sample)
    for wd, tf in wd_counter.most_common():
        tf_dict[wd] = tf / total_1
    # 二元詞
    finder_2 = BigramCollocationFinder.from_words(sample)
    for wd, tf in finder_2.ngram_fd.most_common():
        s = "%s %s" % (wd[0], wd[1])
        tf_dict[s] = tf / (total_1-1)
    # 計算tf-idf特徵
    for j in range(cn):
        wd = feature_map[j]
        tf = tf_dict.get(wd, 0)
        if tf == 0:
            continue
        idf = idf_map[wd]
        train_x[i, j] = tf * idf
with open("train_data_v2", "w") as fw:
    for i in range(len(data_x_list)):
        fw.write("%s\t%s\t%s\t%s\t%s\n" % (uid, y[i], json.dumps(list(train_x[i]))))

還沒做實驗就如此多的步驟。實際上實驗前的分析處理和特徵工程十分重要,應該多做工作,爲提高效率也該存儲一些中間結果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章