机器学习建模全流程及资料总结(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]))))

还没做实验就如此多的步骤。实际上实验前的分析处理和特征工程十分重要,应该多做工作,为提高效率也该存储一些中间结果。

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