第10章特征工程python
特征工程是整个机器学习中很是重要的一部分,如何对数据进行特征提取对最终结果的影响很是大。在建模过程当中,通常会优先考虑算法和参数,可是数据特征才决定了总体结果的上限,而算法和参数只决定了如何逼近这个上限。特征工程其实就是要从原始数据中找到最有价值的信息,并转换成计算机所能读懂的形式。本章结合数值数据与文本数据来分别阐述如何进行数值特征与文本特征的提取。算法
10.1数值特征数据库
实际数据中,最多见的就是数值特征,本节介绍几种经常使用的数值特征提取方法与函数。首先仍是读取一份数据集,并取其中的部分特征来作实验,不用考虑数据特征的具体含义,只进行特征操做便可。服务器
10.1.1字符串编码网络
1 import pandas as pd 2 import numpy as np 3 4 vg_df = pd.read_csv('datasets/vgsales.csv', encoding = "ISO-8859-1") 5 vg_df[['Name', 'Platform', 'Year', 'Genre', 'Publisher']].iloc[1:7]
上述代码生成的数据中不少特征指标都是字符串,首先假设Genre列是最终的分类结果标签,可是计算机可不认识这些字符串,此时就须要将字符转换成数值。app
1 genres = np.unique(vg_df['Genre']) 2 genres
array(['Action', 'Adventure', 'Fighting', 'Misc', 'Platform', 'Puzzle', 'Racing', 'Role-Playing', 'Shooter', 'Simulation', 'Sports', 'Strategy'], dtype=object)
读入数据后,最多见的状况就是不少特征并非数值类型,而是用字符串来描述的,打印结果后发现,Genre列一共有12个不一样的属性值,将其转换成数值便可,最简单的方法就是用数字进行映射:dom
1 from sklearn.preprocessing import LabelEncoder 2 3 gle = LabelEncoder() 4 genre_labels = gle.fit_transform(vg_df['Genre']) 5 genre_mappings = {index: label for index, label in enumerate(gle.classes_)} 6 genre_mappings
{0: 'Action', 1: 'Adventure', 2: 'Fighting', 3: 'Misc', 4: 'Platform', 5: 'Puzzle', 6: 'Racing', 7: 'Role-Playing', 8: 'Shooter', 9: 'Simulation', 10: 'Sports', 11: 'Strategy'}
使用sklearn工具包中的LabelEncoder()函数能够快速地完成映射工做,默认是从数值0开始,fit_transform()是实际执行的操做,自动对属性特征进行映射操做。变换完成以后,能够将新获得的结果加入原始DataFrame中对比一下:机器学习
1 vg_df['GenreLabel'] = genre_labels 2 vg_df[['Name', 'Platform', 'Year', 'Genre', 'GenreLabel']].iloc[1:7]
此时全部的字符型特征就转换成相应的数值,也能够自定义一份映射。异步
1 poke_df = pd.read_csv('datasets/Pokemon.csv', encoding='utf-8') 2 poke_df.head()
1 poke_df = poke_df.sample(random_state=1, frac=1).reset_index(drop=True) 2 3 np.unique(poke_df['Generation'])
array(['Gen 1', 'Gen 2', 'Gen 3', 'Gen 4', 'Gen 5', 'Gen 6'], dtype=object)
这份数据集中一样有多个属性值须要映射,也能够本身动手写一个map函数,对应数值就从1开始吧:
1 gen_ord_map = {'Gen 1': 1, 'Gen 2': 2, 'Gen 3': 3, 2 'Gen 4': 4, 'Gen 5': 5, 'Gen 6': 6} 3 4 poke_df['GenerationLabel'] = poke_df['Generation'].map(gen_ord_map) 5 poke_df[['Name', 'Generation', 'GenerationLabel']].iloc[4:10]
对于简单的映射操做,不管本身完成仍是使用工具包中现成的命令都很是容易,可是更多的时候,对这种属性特征能够选择独热编码,虽然操做稍微复杂些,但从结果上观察更清晰:
1 from sklearn.preprocessing import OneHotEncoder, LabelEncoder 2 3 # 完成LabelEncoder 4 gen_le = LabelEncoder() 5 gen_labels = gen_le.fit_transform(poke_df['Generation']) 6 poke_df['Gen_Label'] = gen_labels 7 8 poke_df_sub = poke_df[['Name', 'Generation', 'Gen_Label', 'Legendary']] 9 10 # 完成OneHotEncoder 11 gen_ohe = OneHotEncoder() 12 gen_feature_arr = gen_ohe.fit_transform(poke_df[['Gen_Label']]).toarray() 13 gen_feature_labels = list(gen_le.classes_) 14 15 # 将转换好的特征组合到dataframe中 16 gen_features = pd.DataFrame(gen_feature_arr, columns=gen_feature_labels) 17 poke_df_ohe = pd.concat
18 poke_df_ohe.head()
上述代码首先导入了OneHotEncoder工具包,对数据进行数值映射操做,又进行独热编码。输出结果显示,独热编码至关于先把全部可能状况进行展开,而后分别用0和1表示实际特征状况,0表明不是当前列特征,1表明是当前列特征。例如,当Gen_Label=3时,对应的独热编码就是,Gen4为1,其他位置都为0(注意原索引从0开始,Gen_Label=3,至关于第4个位置)。
上述代码看起来有点麻烦,那么有没有更简单的方法呢?其实直接使用Pandas工具包更方便:
1 gen_dummy_features = pd.get_dummies(poke_df['Generation'], drop_first=True) 2 pd.concat([poke_df[['Name', 'Generation']], gen_dummy_features], axis=1).iloc[4:10]
Get_dummies()函数能够完成独热编码的工做,当特征较多时,一个个命名太麻烦,此时能够直接指定一个前缀用于标识:
1 gen_onehot_features = pd.get_dummies(poke_df['Generation'],prefix = 'one-hot') 2 pd.concat([poke_df[['Name', 'Generation']], gen_onehot_features], axis=1).iloc[4:10]
如今全部执行独热编码的特征所有带上“one-hot”前缀了,对比发现仍是get_dummies()函数更好用,1行代码就能解决问题。
1 poke_df = pd.read_csv('datasets/Pokemon.csv', encoding='utf-8') 2 poke_df.head()
10.1.2二值与多项式特征
接下来打开一份音乐数据集:
1 popsong_df = pd.read_csv('datasets/song_views.csv', encoding='utf-8') 2 popsong_df.head(10)
数据中包括不一样用户对歌曲的播放量,能够发现不少歌曲的播放量都是0,表示该用户尚未播放过此音乐,这个时候能够设置一个二值特征,以表示用户是否听过该歌曲:
1 watched = np.array(popsong_df['listen_count']) 2 watched[watched >= 1] = 1 3 popsong_df['watched'] = watched 4 popsong_df.head(10)
新加入的watched特征表示歌曲是否被播放,一样也可使用sklearn工具包中的Binarizer来完成二值特征:
1 from sklearn.preprocessing import Binarizer 2 3 bn = Binarizer(threshold=0.9) 4 pd_watched = bn.transform([popsong_df['listen_count']])[0] 5 popsong_df['pd_watched'] = pd_watched 6 popsong_df.head(10)
特征的变换方法还有不少,还能够对其进行各类组合。接下来登场的就是多项式特征,例若有a、b两个特征,那么它的2次多项式为(1,a,b,a2,ab,b2),下面经过sklearn工具包完成变换操做:
1 poke_df = pd.read_csv('datasets/Pokemon.csv', encoding='utf-8') 2 atk_def = poke_df[['Attack', 'Defense']] 3 atk_def.head() 4 5 from sklearn.preprocessing import PolynomialFeatures 6 7 pf = PolynomialFeatures(degree=2, interaction_only=False, include_bias=False) 8 res = pf.fit_transform(atk_def) 9 res[:5]
Attack Defense
0 49 49
1 62 63
2 82 83
3 100 123
4 52 43
array([[ 49., 49., 2401., 2401., 2401.], [ 62., 63., 3844., 3906., 3969.], [ 82., 83., 6724., 6806., 6889.], [ 100., 123., 10000., 12300., 15129.], [ 52., 43., 2704., 2236., 1849.]])
PolynomialFeatures()函数涉及如下3个参数。
为了更清晰地展现,能够加上操做的列名:
1 intr_features = pd.DataFrame(res, columns=['Attack', 'Defense', 'Attack^2', 'Attack x Defense', 'Defense^2']) 2 intr_features.head(5)
10.1.3连续值离散化
连续值离散化的操做很是实用,不少时候都须要对连续值特征进行这样的处理,效果如何还得实际经过测试集来观察,但在特征工程构造的初始阶段,确定仍是但愿可行的路线越多越好。
1 cc_survey_df = pd.read_csv('datasets/fcc_2016_coder_survey_subset.csv', encoding='utf-8') 2 fcc_survey_df[['ID.x', 'EmploymentField', 'Age', 'Income']].head()
上述代码读取了一份带有年龄信息的数据集,接下来要对年龄特征进行离散化操做,也就是划分红一个个区间,实际操做以前,能够观察其分布状况:
1 import pandas as pd 2 import matplotlib.pyplot as plt 3 import matplotlib as mpl 4 import numpy as np 5 import scipy.stats as spstats 6 7 %matplotlib inline 8 mpl.style.reload_library() 9 mpl.style.use('classic') 10 mpl.rcParams['figure.facecolor'] = (1, 1, 1, 0) 11 mpl.rcParams['figure.figsize'] = [6.0, 4.0] 12 mpl.rcParams['figure.dpi'] = 100 13 14 fig, ax = plt.subplots() 15 fcc_survey_df['Age'].hist(color='#A9C5D3') 16 ax.set_title('Developer Age Histogram', fontsize=12) 17 ax.set_xlabel('Age', fontsize=12) 18 ax.set_ylabel('Frequency', fontsize=12)
上述输出结果显示,年龄特征的取值范围在10~90之间。所谓离散化,就是将一段区间上的数据映射到一个组中,例如按照年龄大小可分红儿童、青年、中年、老年等。简单起见,这里直接按照相同间隔进行划分:
1 fcc_survey_df['Age_bin_round'] = np.array(np.floor(np.array(fcc_survey_df['Age']) / 10.)) 2 fcc_survey_df[['ID.x', 'Age', 'Age_bin_round']].iloc[1071:1076]
上述代码中,np.floor表示向下取整,例如,对3.3取整后,获得的就是3。这样就完成了连续值的离散化,全部数值都划分到对应的区间上。
还能够利用分位数进行分箱操做,换一个特征试试,先来看看收入的状况:
1 #fcc_survey_df[['ID.x', 'Age', 'Income']].iloc[4:9] 2 fig, ax = plt.subplots() 3 fcc_survey_df['Income'].hist(bins=30, color='#A9C5D3') 4 ax.set_title('Developer Income Histogram', fontsize=12) 5 ax.set_xlabel('Developer Income', fontsize=12) 6 ax.set_ylabel('Frequency', fontsize=12)
分位数就是按照比例来划分,也能够自定义合适的比例:
1 quantile_list = [0, .25, .5, .75, 1.] 2 quantiles = fcc_survey_df['Income'].quantile(quantile_list) 3 quantiles
0.00 6000.0
0.25 20000.0
0.50 37000.0
0.75 60000.0
1.00 200000.0
Name: Income, dtype: float64
1 fig, ax = plt.subplots() 2 fcc_survey_df['Income'].hist(bins=30, color='#A9C5D3') 3 4 for quantile in quantiles: 5 qvl = plt.axvline(quantile, color='r') 6 ax.legend([qvl], ['Quantiles'], fontsize=10) 7 8 ax.set_title('Developer Income Histogram with Quantiles', fontsize=12) 9 ax.set_xlabel('Developer Income', fontsize=12) 10 ax.set_ylabel('Frequency', fontsize=12)
Quantile函数就是按照选择的比例获得对应的切分值,再应用到数据中进行离散化操做便可:
1 quantile_labels = ['0-25Q', '25-50Q', '50-75Q', '75-100Q'] 2 fcc_survey_df['Income_quantile_range'] = pd.qcut(fcc_survey_df['Income'], 3 q=quantile_list) 4 fcc_survey_df['Income_quantile_label'] = pd.qcut(fcc_survey_df['Income'], 5 q=quantile_list, labels=quantile_labels) 6 fcc_survey_df[['ID.x', 'Age', 'Income', 7 'Income_quantile_range', 'Income_quantile_label']].iloc[4:9]
此时全部数据都完成了分箱操做,拿到实际数据后如何指定比例就得看具体问题,并无固定不变的规则,根据实际业务来判断才是最科学的。
10.1.4对数与时间变换
拿到某列数据特征后,其分布多是各类各样的状况,可是,不少机器学习算法但愿预测的结果值可以呈现高斯分布,这就须要再对其进行变换,最直接的就是对数变换:
1 fcc_survey_df['Income_log'] = np.log((1+ fcc_survey_df['Income'])) 2 fcc_survey_df[['ID.x', 'Age', 'Income', 'Income_log']].iloc[4:9]
1 income_log_mean = np.round(np.mean(fcc_survey_df['Income_log']), 2) 2 3 fig, ax = plt.subplots() 4 fcc_survey_df['Income_log'].hist(bins=30, color='#A9C5D3') 5 plt.axvline(income_log_mean, color='r') 6 ax.set_title('Developer Income Histogram after Log Transform', fontsize=12) 7 ax.set_xlabel('Developer Income (log scale)', fontsize=12) 8 ax.set_ylabel('Frequency', fontsize=12) 9 ax.text(11.5, 450, r'$\mu$='+str(income_log_mean), fontsize=10)
通过对数变换以后,特征分布更接近高斯分布,虽然还不够完美,但仍是有些进步的,感兴趣的读者还能够进一步了解cox-box变换,目的都是相同的,只是在公式上有点区别。
时间相关数据也是能够提取出不少特征,例如年、月、日、小时等,甚至上旬、中旬、下旬、工做时间、下班时间等均可以看成算法的输入特征。
1 import datetime 2 import numpy as np 3 import pandas as pd 4 from dateutil.parser import parse 5 import pytz 6 7 import numpy as np 8 import pandas as pd 9 10 time_stamps = ['2015-03-08 10:30:00.360000+00:00', '2017-07-13 15:45:05.755000-07:00', 11 '2012-01-20 22:30:00.254000+05:30', '2016-12-25 00:30:00.000000+10:00'] 12 df = pd.DataFrame(time_stamps, columns=['Time']) 13 df
Time
0 2015-03-08 10:30:00.360000+00:00
1 2017-07-13 15:45:05.755000-07:00
2 2012-01-20 22:30:00.254000+05:30
3 2016-12-25 00:30:00.000000+10:00
接下来就要获得各类细致的时间特征,若是用的是标准格式的数据,也能够直接调用其属性,更方便一些:
ts_objs = np.array([pd.Timestamp(item) for item in np.array(df.Time)]) df['TS_obj'] = ts_objs ts_objs
array([Timestamp('2015-03-08 10:30:00.360000+0000', tz='UTC'), Timestamp('2017-07-13 15:45:05.755000-0700', tz='pytz.FixedOffset(-420)'), Timestamp('2012-01-20 22:30:00.254000+0530', tz='pytz.FixedOffset(330)'), Timestamp('2016-12-25 00:30:00+1000', tz='pytz.FixedOffset(600)')], dtype=object)
1 df['Year'] = df['TS_obj'].apply(lambda d: d.year) 2 df['Month'] = df['TS_obj'].apply(lambda d: d.month) 3 df['Day'] = df['TS_obj'].apply(lambda d: d.day) 4 df['DayOfWeek'] = df['TS_obj'].apply(lambda d: d.dayofweek) 5 # df['DayName'] = df['TS_obj'].apply(lambda d: d.weekday_name)# 6 # AttributeError: 'Timestamp' object has no attribute 'weekday_name' 7 df['DayOfYear'] = df['TS_obj'].apply(lambda d: d.dayofyear) 8 df['WeekOfYear'] = df['TS_obj'].apply(lambda d: d.weekofyear) 9 df['Quarter'] = df['TS_obj'].apply(lambda d: d.quarter) 10 11 # df[['Time', 'Year', 'Month', 'Day', 'Quarter', 12 # 'DayOfWeek', 'DayName', 'DayOfYear', 'WeekOfYear']] 13 df[['Time', 'Year', 'Month', 'Day', 'Quarter', 14 'DayOfWeek', 'DayOfYear', 'WeekOfYear']]
1 hour_bins = [-1, 5, 11, 16, 21, 23] 2 bin_names = ['Late Night', 'Morning', 'Afternoon', 'Evening', 'Night'] 3 df['TimeOfDayBin'] = pd.cut(df['Hour'], 4 bins=hour_bins, labels=bin_names) 5 df[['Time', 'Hour', 'TimeOfDayBin']]
Time Hour TimeOfDayBin 0 2015-03-08 10:30:00.360000+00:00 10 Morning 1 2017-07-13 15:45:05.755000-07:00 15 Afternoon 2 2012-01-20 22:30:00.254000+05:30 22 Night 3 2016-12-25 00:30:00.000000+10:00 0 Late Night
原始时间特征肯定后,居然分出这么多小特征。当拿到具体时间数据后,还能够整合一些相关信息,例如天气状况,气象台数据很轻松就能够拿到,对应的温度、降雨等指标也就都有了。
10.2文本特征
文本特征常常在数据中出现,一句话、一篇文章都是文本特征。仍是一样的问题,计算机依旧不认识它们,因此首先要将其转换成数值,也就是向量。关于文本特征的提取方式,这里先作简单介绍,在下一章的新闻分类任务中,还会详细解释文本特征提取操做。
10.2.1词袋模型
先来构造一个数据集,简单起见就用英文表示,若是是中文数据,还须要先进行分词操做,英文中默认就是分好词的结果:
1 import pandas as pd 2 import numpy as np 3 import re 4 import nltk #pip install nltk 5 #jieba 6 7 corpus = ['The sky is blue and beautiful.', 8 'Love this blue and beautiful sky!', 9 'The quick brown fox jumps over the lazy dog.', 10 'The brown fox is quick and the blue dog is lazy!', 11 'The sky is very blue and the sky is very beautiful today', 12 'The dog is lazy but the brown fox is quick!' 13 ] 14 labels = ['weather', 'weather', 'animals', 'animals', 'weather', 'animals'] 15 corpus = np.array(corpus) 16 corpus_df = pd.DataFrame({'Document': corpus, 17 'Category': labels}) 18 corpus_df = corpus_df[['Document', 'Category']] 19 corpus_df
Document Category 0 The sky is blue and beautiful. weather 1 Love this blue and beautiful sky! weather 2 The quick brown fox jumps over the lazy dog. animals 3 The brown fox is quick and the blue dog is lazy! animals 4 The sky is very blue and the sky is very beaut... weather 5 The dog is lazy but the brown fox is quick! animals
在天然语言处理中有一个很是实用的NLTK工具包,使用前须要先安装该工具包,可是,安装完以后,它至关于一个空架子,里面没有实际的功能,须要有选择地安装部分插件(见图10-1)。
图10-1 NLTK工具包
执行nltk.download()会跳出安装界面,选择须要的功能进行安装便可。不只如此,NLTK工具包还提供了不少数据集供咱们练习使用,功能仍是很是强大的。
NLTK安装能够参考这里:《数据分析实战-托马兹.卓巴斯》读书笔记第9章--天然语言处理NLTK(分析文本、词性标注、主题抽取、文本数据分类)
1 nltk.download() 2 # nltk.download('wordnet') 3 #并把文件从默认的路径C:\Users\tony zhang\AppData\Roaming\nltk_data\移动到D:\download\nltk_data\
1 from nltk import data 2 data.path.append(r'D:\download\nltk_data') # 这里的路径须要换成本身数据文件下载的路径
对于文本数据,第一步确定要进行预处理操做,基本的套路就是去掉各类特殊字符,还有一些用处不大的停用词。
所谓停用词就是该词对最终结果影响不大,例如,“咱们”“今天”“可是”等词语就属于停用词。
1 import nltk 2 from nltk import data 3 data.path.append(r'D:\download\nltk_data') # 这里的路径须要换成本身数据文件下载的路径 4 #加载停用词 5 wpt = nltk.WordPunctTokenizer() 6 stop_words = nltk.corpus.stopwords.words('english') 7 8 def normalize_document(doc): 9 # 去掉特殊字符 10 doc = re.sub(r'[^a-zA-Z0-9\s]', '', doc, re.I) 11 # 转换成小写 12 doc = doc.lower() 13 doc = doc.strip() 14 # 分词 15 tokens = wpt.tokenize(doc) 16 # 去停用词 17 filtered_tokens = [token for token in tokens if token not in stop_words] 18 # 从新组合成文章 19 doc = ' '.join(filtered_tokens) 20 return doc 21 22 normalize_corpus = np.vectorize(normalize_document)
1 norm_corpus = normalize_corpus(corpus) 2 norm_corpus 3 #The sky is blue and beautiful.
array(['sky blue beautiful', 'love blue beautiful sky', 'quick brown fox jumps lazy dog', 'brown fox quick blue dog lazy', 'sky blue sky beautiful today', 'dog lazy brown fox quick'], dtype='<U30')
像the、this等对整句话的主题不起做用的词也所有去掉,下面就要对文本进行特征提取,也就是把每句话都转换成数值向量。
1 from sklearn.feature_extraction.text import CountVectorizer 2 print (norm_corpus) 3 cv = CountVectorizer(min_df=0., max_df=1.) 4 cv.fit(norm_corpus) 5 print (cv.get_feature_names()) 6 cv_matrix = cv.fit_transform(norm_corpus) 7 cv_matrix = cv_matrix.toarray() 8 cv_matrix
['sky blue beautiful' 'love blue beautiful sky' 'quick brown fox jumps lazy dog' 'brown fox quick blue dog lazy' 'sky blue sky beautiful today' 'dog lazy brown fox quick'] ['beautiful', 'blue', 'brown', 'dog', 'fox', 'jumps', 'lazy', 'love', 'quick', 'sky', 'today'] array([[1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0], [1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0], [0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0], [0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0], [1, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1], [0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0]], dtype=int64)
1 vocab = cv.get_feature_names() 2 pd.DataFrame(cv_matrix, columns=vocab)
文章中出现多少个不一样的词,其向量的维度就是多大,再依照其出现的次数和位置,就能够把向量构造出来。上述代码只考虑单个词,其实还能够把词和词之间的组合考虑进来,原理仍是同样的,接下来就要多考虑组合,从结果来看更直接:
1 bv = CountVectorizer(ngram_range=(2,2)) 2 bv_matrix = bv.fit_transform(norm_corpus) 3 bv_matrix = bv_matrix.toarray() 4 vocab = bv.get_feature_names() 5 pd.DataFrame(bv_matrix, columns=vocab)
上述代码设置了ngram_range参数,至关于要考虑词的上下文,此处只考虑两两组合的状况,你们也能够将ngram_range参数设置成(1,2),这样既包括一个词也包括两个词组合的状况。
词袋模型的原理和操做都十分简单,可是这样作出来的向量是没有灵魂的。不管是一句话仍是一篇文章,都是有前后顺序的,但在词袋模型中,却只考虑词频,而且每一个词的重要程度彻底和其出现的次数相关,一般状况下,文章向量会是一个很是大的稀疏矩阵,并不利于计算。
词袋模型的问题看起来仍是不少,其优势也是有的,简单方便。在实际建模任务中,还不能肯定哪一种特征提取方法效果更好,因此,各类方法都须要尝试。
10.2.2经常使用文本特征构造方法
文本特征提取方法还不少,下面介绍一些经常使用的构造方法,在实际任务中,不只能够选择常规套路,也能够组合使用一些野路子。
(1)TF-IDF特征。虽然词袋模型只考虑了词频,没考虑词自己的含义,但在TF-IDF中,会考虑每一个词的重要程度,后续再详细讲解TF-IDF关键词的提取方法,先来看看其能获得的结果:
1 from sklearn.feature_extraction.text import TfidfVectorizer 2 tv = TfidfVectorizer(min_df=0., max_df=1., use_idf=True) 3 tv_matrix = tv.fit_transform(norm_corpus) 4 tv_matrix = tv_matrix.toarray() 5 6 vocab = tv.get_feature_names() 7 pd.DataFrame(np.round(tv_matrix, 2), columns=vocab)
上述输出结果显示,每一个词都获得一个小数结果,而且有大小之分,代表其在该篇文章中的重要程度,下一章的新闻分类任务还会详细讨论。
(2)类似度特征。只要肯定了特征,而且所有转换成数值数据,才能够计算它们之间的类似性,计算方法也比较多,这里用余弦类似性来举例,sklearn工具包中已经有实现好的功能,直接将上例中TF-IDF特征提取结果看成输入便可:
1 from sklearn.metrics.pairwise import cosine_similarity 2 3 similarity_matrix = cosine_similarity(tv_matrix) 4 similarity_df = pd.DataFrame(similarity_matrix) 5 similarity_df
(3)聚类特征。聚类就是把数据按堆划分,最后每堆给出一个实际的标签,须要先把数据转换成数值特征,而后计算其聚类结果,其结果也能够看成离散型特征(聚类算法会在第16章讲解)。
1 from sklearn.cluster import KMeans 2 3 km = KMeans(n_clusters=2) 4 km.fit_transform(similarity_df) 5 cluster_labels = km.labels_ 6 cluster_labels = pd.DataFrame(cluster_labels, columns=['ClusterLabel']) 7 pd.concat([corpus_df, cluster_labels], axis=1)
(4)主题模型。主题模型是无监督方法,输入就是处理好的语料库,能够获得主题类型以及其中每个词的权重结果:
1 from sklearn.decomposition import LatentDirichletAllocation 2 3 # help(LatentDirichletAllocation) 4 # lda = LatentDirichletAllocation(n_topics=2, max_iter=100, random_state=42) 5 # n_components : int, optional (default=10) 6 # | Number of topics. 7 8 lda = LatentDirichletAllocation(n_components=2, max_iter=100, random_state=42) 9 dt_matrix = lda.fit_transform(tv_matrix) 10 features = pd.DataFrame(dt_matrix, columns=['T1', 'T2']) 11 features 12 13 tt_matrix = lda.components_ 14 for topic_weights in tt_matrix: 15 topic = [(token, weight) for token, weight in zip(vocab, topic_weights)] 16 topic = sorted(topic, key=lambda x: -x[1]) 17 topic = [item for item in topic if item[1] > 0.6] 18 print(topic) 19 print()
T1 T2
0 0.190548 0.809452
1 0.176804 0.823196
2 0.846184 0.153816
3 0.814863 0.185137
4 0.180516 0.819484
5 0.839172 0.160828
[('brown', 1.7273638692668465), ('dog', 1.7273638692668465), ('fox', 1.7273638692668465), ('lazy', 1.7273638692668465), ('quick', 1.7273638692668465),
('jumps', 1.0328325272484777), ('blue', 0.7731573162915626)] [('sky', 2.264386643135622), ('beautiful', 1.9068269319456903), ('blue', 1.7996282104933266), ('love', 1.148127242397004),
('today', 1.0068251160429935)]
上述代码设置n_topicsn_components =2,至关于要获得两种主题,最后的结果就是各个主题不一样关键词的权重,看起来这件事处理得还不错,使用无监督的方法,也能获得这么多关键的指标。笔者认为,LDA主题模型并非很实用,获得的效果一般也是通常,因此,并不建议你们用其进行特征处理或者建模任务,熟悉一下就好。
(5)词向量模型。前面介绍的几种特征提取方法仍是比较容易理解的,再来看看词向量模型,也就是常说的word2vec,其基本原理是基于神经网络的。先来通俗地解释一下,首先对每一个词进行初始化操做,例如,每一个词都是长度为10的一个随机向量。接下来,模型会对每一个词及其上下文进行预测,例如输入是向量“回家”,输出就是“吃饭”,全部的输入数据和输出标签都是语料库中的上下文,因此标签并不须要特地指定。此时不仅要经过优化算法选择合适的权重参数,例如梯度降低,输入的向量也会随之改变,也就是向量“回家”一开始是随机的,在每次迭代过程当中都会不断改变,直到获得一个合适的结果。
词向量模型是现阶段天然语言处理中最常使用的方法,并赋予每一个词实际的空间含义,回顾一下,使用前面讲述过的特征提取方法获得的向量都没有实际意义,只是数值,但在词向量模型中,每一个词在空间中都是有实际意义的,例如,“喜欢”和“爱”这两个词在空间中比较接近,由于其表达的含义相似,可是它们和“手机”就离得比较远,由于关系不大。讲解完神经网络以后,在第20章的影评分类任务中有它的实际应用案例。当你们使用时,需首先将文本中每个词的向量构造出来,最经常使用的工具包就是Gensim,其中有语料库:
1 from gensim.models import word2vec 2 from nltk import data 3 data.path.append(r'D:\download\nltk_data') # 这里的路径须要换成本身数据文件下载的路径 4 wpt = nltk.WordPunctTokenizer() 5 tokenized_corpus = [wpt.tokenize(document) for document in norm_corpus] 6 7 # 须要设置一些参数 8 feature_size = 10 # 词向量维度 9 window_context = 10 # 滑动窗口 10 min_word_count = 1 # 最小词频 11 12 w2v_model = word2vec.Word2Vec(tokenized_corpus, size=feature_size, 13 window=window_context, min_count = min_word_count) 14 15 w2v_model.wv['sky']
array([-0.02571594, -0.02806569, -0.01904523, -0.03620922, 0.01884929, -0.04410132, 0.02005241, -0.00504071, 0.01696092, 0.01301065], dtype=float32)
1 def average_word_vectors(words, model, vocabulary, num_features): 2 3 feature_vector = np.zeros((num_features,),dtype="float64") 4 nwords = 0. 5 6 for word in words: 7 if word in vocabulary: 8 nwords = nwords + 1. 9 feature_vector = np.add(feature_vector, model[word]) 10 11 if nwords: 12 feature_vector = np.divide(feature_vector, nwords) 13 14 return feature_vector 15 16 17 def averaged_word_vectorizer(corpus, model, num_features): 18 vocabulary = set(model.wv.index2word) 19 features = [average_word_vectors(tokenized_sentence, model, vocabulary, num_features) 20 for tokenized_sentence in corpus] 21 return np.array(features)
1 w2v_feature_array = averaged_word_vectorizer(corpus=tokenized_corpus, model=w2v_model, 2 num_features=feature_size) 3 pd.DataFrame(w2v_feature_array) #lstm
输出结果就是输入预料中的每个词都转换成向量,词向量的应用十分普遍,现阶段一般都是将其和神经网络结合在一块儿来搭配使用(后续案例就会看到其强大的战斗力)。
10.3论文与benchmark
在数据挖掘任务中,特征工程尤其重要,数据的字段中可能包含各类各样的信息,如何提取出最有价值的特征呢?你们第一个想到的多是经验方法,回顾一下以前处理其余数据的方法或者一些通用的套路,但确定都不肯定方法是否得当,并且要把每一个想法都实践一遍也不太现实。这里给你们推荐一个套路,结合论文与benchmark来找解决方案,相信会事半功倍。
最好的方法就是从论文入手,你们也能够把论文看成是一个实际任务的解决方案,对于较复杂的任务,你可能没有深刻研究过,可是前人已经探索过其中的方法,论文就是他们对好的思路、实验结果以及其中遇到各类问题的总结。若是把他们的方法加以研究和改进,再应用到实际任务中,是否是看起来很棒?
可是,如何找到合适的论文做为参考呢?若是不是专门作某一领域,可能对这些资源并非很熟悉,这里给你们推荐benchmark,翻译过来叫做“基准”。其实它就是一个数据库,里面有某一领域的数据集,而且收录不少该领域的论文,还有测试结果。
图10-2所示为迪哥曾经作过实验的benchmark,首页就是它的总体介绍。例如,对于一我的体关键点的图像识别任务,其中不只提供了一份人体姿态的数据集,还收录不少篇相关论文,一般能被benchmark收录进来的论文都是被证实过效果很是不错的。
图10-2 MPII人体姿态识别benchmark
图10-3中截取了其收录的一部分论文,从2013—2018年的姿态识别经典论文均可以在此找到。若是你们熟悉计算机视觉领域,就能看出这些论文的发表级别很是高,右侧有其实验结果,包括头部、肩膀、各个关节的识别效果。能够发现,随着年份的增长,效果逐步提高,如今作得已经很成熟了。
图10-3 收录论文结果
对于不会选择合适论文的同窗,仍是看经典论文吧,直接搜索出来的论文可能价值通常,benchmark推荐的论文都是经典且有学习价值的。
Benchmark还有一个特色,就是其收录的论文不少都是有公开代码的。图10-四、图10-5就是打开的论文主页,不只有实验的源码,还提供了训练好的模型,不管是实际完成任务仍是学习阶段,都对你们有很大的帮助。假设你须要作一我的体姿态识别的任务,这时候你不仅手里有一份当下效果最好的识别代码,还有原做者训练好的模型,直接部署到服务器,不出一天你就能够说:任务基本完成了,目前来看没有比这个效果更好的了(这为咱们的工做提供了一条捷径)。
▲图10-4 论文公开源码(1)
▲图10-5 论文公开源码(2)
在初学阶段最好将理论与实践结合在一块儿,论文固然就是指导思想,告诉你们一步步该怎么作,其提供的代码就是实践方法。笔者认为没有源码的学习是很是痛苦的,由于论文当中不少细节都简化了,估计不少同窗也是这样的想法,看代码反而能更直接地理解论文的思想。
如何应用源码呢?一般拿到的工做都是比较复杂的,直接看一行行代码,估计都挺费劲,最好的办法就是一步步debug,看看其中每一步完成了什么,再结合论文就好理解了。
10.3图像特征
1 pip install skimage
1 import skimage 2 import numpy as np 3 import pandas as pd 4 import matplotlib.pyplot as plt 5 from skimage import io 6 #opencv tensorflow 7 %matplotlib inline 8 9 cat = io.imread('./datasets/cat.png') 10 dog = io.imread('./datasets/dog.png') 11 df = pd.DataFrame(['Cat', 'Dog'], columns=['Image']) 12 13 14 print(cat.shape, dog.shape)
(168, 300, 3) (168, 300, 3)
1 cat #0-255,越小的值表明越暗,越大的值越亮
array([[[114, 105, 90], [113, 104, 89], [112, 103, 88], ..., [127, 130, 121], [130, 133, 124], [133, 136, 127]], [[113, 104, 89], [112, 103, 88], [111, 102, 87], ..., [129, 132, 125], [132, 135, 128], [135, 138, 131]], [[111, 102, 87], [111, 102, 87], [110, 101, 86], ..., [132, 134, 133], [136, 138, 137], [139, 141, 140]], ..., [[ 32, 26, 28], [ 32, 26, 28], [ 30, 24, 26], ..., [131, 131, 131], [131, 131, 131], [130, 130, 130]], [[ 33, 27, 29], [ 32, 26, 28], [ 31, 25, 27], ..., [131, 131, 131], [131, 131, 131], [130, 130, 130]], [[ 33, 27, 29], [ 32, 26, 28], [ 31, 25, 27], ..., [131, 131, 131], [131, 131, 131], [130, 130, 130]]], dtype=uint8)
1 #coffee = skimage.transform.resize(coffee, (300, 451), mode='reflect') 2 fig = plt.figure(figsize = (8,4)) 3 ax1 = fig.add_subplot(1,2, 1) 4 ax1.imshow(cat) 5 ax2 = fig.add_subplot(1,2, 2) 6 ax2.imshow(dog)
<matplotlib.image.AxesImage at 0x233c9b53988>
1 dog_r = dog.copy() # Red Channel 2 dog_r[:,:,1] = dog_r[:,:,2] = 0 # set G,B pixels = 0 3 dog_g = dog.copy() # Green Channel 4 dog_g[:,:,0] = dog_r[:,:,2] = 0 # set R,B pixels = 0 5 dog_b = dog.copy() # Blue Channel 6 dog_b[:,:,0] = dog_b[:,:,1] = 0 # set R,G pixels = 0 7 8 plot_image = np.concatenate((dog_r, dog_g, dog_b), axis=1) 9 plt.figure(figsize = (10,4)) 10 plt.imshow(plot_image)
1 dog_r
array([[[160, 0, 0], [160, 0, 0], [160, 0, 0], ..., [113, 0, 0], [113, 0, 0], [112, 0, 0]], [[160, 0, 0], [160, 0, 0], [160, 0, 0], ..., [113, 0, 0], [113, 0, 0], [112, 0, 0]], [[160, 0, 0], [160, 0, 0], [160, 0, 0], ..., [113, 0, 0], [113, 0, 0], [112, 0, 0]], ..., [[165, 0, 0], [165, 0, 0], [165, 0, 0], ..., [212, 0, 0], [211, 0, 0], [210, 0, 0]], [[165, 0, 0], [165, 0, 0], [165, 0, 0], ..., [210, 0, 0], [210, 0, 0], [209, 0, 0]], [[164, 0, 0], [164, 0, 0], [164, 0, 0], ..., [209, 0, 0], [209, 0, 0], [209, 0, 0]]], dtype=uint8)
灰度图:
1 fig = plt.figure(figsize = (8,4)) 2 ax1 = fig.add_subplot(2,2, 1) 3 ax1.imshow(cgs, cmap="gray") 4 ax2 = fig.add_subplot(2,2, 2) 5 ax2.imshow(dgs, cmap='gray')
<matplotlib.image.AxesImage at 0x1fca2353358>
本章小结:
本章介绍了特征提取的经常使用方法,主要包括数值特征和文本特征,能够说不一样的方法各有其优缺点。在任务起始阶段,应当尽量多地尝试各类可能的提取方法,特征多没关系,实际建模的时候,能够经过实验来筛选,可是少了就没有办法了,因此,在特征工程阶段,仍是要多动脑筋,要提早考虑建模方案。由于一旦涉及海量数据,提取特征但是一个漫长的活,若是只是走一步看一步,效率就会大大下降。
作任务的时候,必定要结合论文,各类解决方案都要进行尝试,最好的方法就是先学学别人是怎么作的,再应用到本身的实际任务中。
第10章完。
该书资源下载,请至异步社区:https://www.epubit.com