【特征工程入门与实践】【特征构建】

填充分类特征

定性填充

  • 对于定性的列,我们可以计算最常见的类别用于填充
X['city'].fillna(X['city'].value_counts().index[0])
  • 当然,我们不可能对于每一个列都这样写,我们需要建立机器学习流水线,构建自定义的转换器,一次性把数据转换好
    使用TransformerMixin作为基类,则自动实现fit_transform()函数
from sklearn.base import TransformerMixin
class CustomCategoryImputer(TransformerMixin):
    def __init__(self,cols=None):
        self.cols = cols
        
    def transform(self,df):
        X = df.copy()
        for col in self.cols:
            X[col].fillna(X[col].value_counts().index[0],inplace=True)
        return X
    
    def fit(self,*_):
        return self

# 对列应用自定义填充器[定类]
cci = CustomCategoryImputer(cols=['city','boolean'])
cci.fit_transform(X)

定量填充

# 自定义定量填充器
from sklearn.impute import SimpleImputer
class CustomQuantitativeImputer(TransformerMixin):
    def __init__(self,cols=None,strategy='mean'):
        self.cols = cols
        self.strategy = strategy
        
    def transform(self,df):
        X = df.copy()
        impute = SimpleImputer(strategy=self.strategy)
        for col in self.cols:
            X[col] = impute.fit_transform(X[[col]])
        return X
    
    def fit(self,*_):
        return self

cqi = CustomQuantitativeImputer(cols=['quantitative_column'],strategy='mean') 
cqi.fit_transform(X)
  • 通过pipeline把它们缝合在一起,不需要重复fit_transform
from sklearn.pipeline import Pipeline
imputer = Pipeline([('quant',cqi),('category',cci)])
imputer.fit_transform(X)

编码分类变量

定类等级编码

将分类数据转换为虚拟变量(dummy variables)。虚拟变量陷阱是原特征有m个类别,若将其转换成m个虚拟变量,会导致变量间出现完全共线性,此时我们应该将其转换为m-1个虚拟变量。

  • 例子:性别有2个类别,变量x1=1代表男, 否则为0;变量x2=1代表女,否则等于0,得到目标 y = w 1 x 1 + w 2 x 2 + b y=w_1x_1+w_2x_2+b y=w1x1+w2x2+b,男x1=[1,0],女x2=[0,1]。因为非男即女,所以x1+x2=1,两个变量之间存在线性关系,共线性问题使得模型参数无法估计。解决办法:使 y = w 1 ( x 1 + x 2 ) + ( w 2 − w 1 ) x 2 + b = ( w 2 − w 1 ) x 2 + w 1 + b y=w_1(x_1+x_2)+(w_2-w_1)x_2+b=(w_2-w_1)x_2+w_1+b y=w1(x1+x2)+(w2w1)x2+b=(w2w1)x2+w1+b。意思是把其中一个变量作为基准从方程式中删去,只通过一个变量x2就能推导出所有信息。因为我们知道x1+x2=1,所以直接就化简到后面这一步。

  • pandas里面方便的方法

pd.get_dummies(X,
              columns=['city','boolean'],  #虚拟化的列
              prefix_sep='_')				#列名前缀用的分隔符
  • 自定义虚拟变量编码器
class CustomDummifier(TransformerMixin): 
    def __init__(self, cols=None): 
        self.cols = cols 
    def transform(self, X): 
        return pd.get_dummies(X, columns=self.cols) 
    def fit(self, *_): 
        return self
        
cd = CustomDummifier(cols=['boolean', 'city']) 
cd.fit_transform(X)

定序等级的编码

定序的特点是数据顺序是有意义的,为了保持顺序,我们使用标签编码器

  • 最简单的编码方法:直接用index
ordering = ['dislike', 'somewhat like', 'like'] 
# 0 是 dislike,1 是 somewhat like,2 是 like

print(X['ordinal_column'].map(lambda x:ordering.index(x)))
  • 其实sklearn自带一个LabelEncoder,我们这里没用是因为它的编码方式不符合我们的要求
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
le.fit_transform(X['ordinal_column'])  #顺序不对

得到array([2, 1, 2, 1, 2, 0])
我们要array([1, 2, 1, 2, 1, 0])
  • 自定义标签编码器
# 自定义标签编码器
class CustomEncoder(TransformerMixin): 
    def __init__(self, col, ordering=None): 
        self.ordering = ordering 
        self.col = col 
    def transform(self, df): 
        X = df.copy() 
        X[self.col] = X[self.col].map(lambda x: self.ordering.index(x))
        return X
    def fit(self,*_):
        return self

ce = CustomEncoder(col='ordinal_column',ordering= ['dislike', 'somewhat like', 
'like'])
ce.fit_transform(X)

连续特征分箱

分箱可以离散化连续值,可能使得分类变量有意义,创建数据的各个范围,例如年龄段

  • 我们使用pandas一个函数叫cut,可以进行数据分箱(binning)
pd.cut(X['quantitative_column'], bins=3)

输出:
0     (-0.52, 6.333]
1    (6.333, 13.167]
2     (-0.52, 6.333]
3    (6.333, 13.167]
4                NaN
5     (13.167, 20.0]
Name: quantitative_column, dtype: category
Categories (3, interval[float64]): [(-0.52, 6.333] < (6.333, 13.167] < (13.167, 20.0]]
pd.cut(X['quantitative_column'], bins=3,labels=False) #把标签换成整数指示器

输出:
0    0.0
1    1.0
2    0.0
3    1.0
4    NaN
5    2.0
Name: quantitative_column, dtype: float64
  • 自定义分箱流水线
class CustomCutter(TransformerMixin): 
    def __init__(self, col, bins, labels=False): 
        self.labels = labels 
        self.bins = bins 
        self.col = col 
    def transform(self, df): 
        X = df.copy() 
        X[self.col] = pd.cut(X[self.col], bins=self.bins, labels=self.labels) 
        return X 
    def fit(self, *_): 
        return self

cc = CustomCutter(col='quantitative_column', bins=3,labels=False) 
cc.fit_transform(X)

流水线处理

前两大节中,我们做了一些数据上的处理转换,现在理一下思路,然后做成流水线。

  1. 使用imputer填充定性与定量的数据
  2. 对boolean和city列做虚拟变量编码
  3. 对ordinal column列进行标签编码
  4. 对quantitative column列做分箱
from sklearn.pipeline import Pipeline
# 1. 设置流水线,按照上面的流水顺序
pipe = Pipeline([("imputer",imputer),('dummify',cd),('encode',ce),('cut',cc)])
#这四个就是上面写的自定义函数

#2.拟合流水线
pipe.fit(X)

#3.进行转换转换
pipe.transform(X)

扩展数值特征

多项式特征

使用sklearn自带的Polynomial-Features类,会创建新的列,是原有列的乘积,用于捕获特征交互。例如 [ a , b ] [a,b] [a,b]的多项式特征为 [ 1 , a , b , a 2 , b 2 , a b ] [1,a,b,a^2,b^2,ab] [1,a,b,a2,b2,ab]

三个重要参数:degree、interaction_only、include_bias

  • degree是多项式特征的阶数,默认值为2
  • interaction_only默认为false,如果为真,表示只生成互相影响的特征,也就是不同
    阶数特征的乘积
  • include_bias默认为True,会生成一列阶数为 0 的偏差列,也就是说列中全是数字 1。
# 多项式特征 , 原有列的乘积

from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2,include_bias=False,interaction_only=False)

#调用fit transform拟合多项式特征
x_poly = poly.fit_transform(X)
x_poly.shape
查看shape可知扩展了很多列,把数据放入dataframe,列标题设为feature name

pd.DataFrame(X_poly, columns=poly.get_feature_names()).head()

在这里插入图片描述
若我们把interaction_only 设置成了 True,会得到下面这种结果:
在这里插入图片描述
对于这些多项式的特征,可以看看相关性矩阵:

sns.heatmap(pd.DataFrame(X_poly, columns=poly.get_feature_names()).corr())

文本的特征构建

文本我就不做太多的描述了,只是简单提及一些方法和实现。

bag of word 词袋法

文档出现次数(词频)作为表示,使用CountVectorizer将文本转为矩阵,每行代表一个文档,每列代表一个单词

from sklearn.feature_extraction.text import CountVectorizer
vect = CountVectorizer(stop_words='english',min_df=.05,ngram_range=(1,5),analyzer='word') 
re = vect.fit_transform(X)
print(re.shape)

stop_words 删除英语停用词(if、a、the, 等等)
min_df只保留至少在5%文档中出现的单词
ngram_range,接受一个元组,代表上下界,(1,5)包括最多5个单词的短语
analyzer默认分析其为单词,我们可以自定义分析器,例如词干提取器

  • 词干提取器
from nltk.stem.snowball import SnowballStemmer 
stemmer = SnowballStemmer('english')
stemmer.stem('interesting')
得到词根“interest”

自己写个函数,然后应用:

def word_tokenize(text, how='lemma'):  #lemma意思是词根
    words = text.split(' ') # 按词分词
    return [stemmer.stem(word) for word in words]

word_tokenize("hello you are very interesting")
输出:['hello', 'you', 'are', 'veri', 'interest']

vect = CountVectorizer(analyzer=word_tokenize) 
_ = vect.fit_transform(X) 
print(_.shape) # 单词变小,特征少了

TF-IDF

词 频 T F = 单 词 出 现 在 文 档 中 的 次 数 / 文 档 总 词 数 词频TF = 单词出现在文档中的次数 / 文档总词数 TF=/
逆 文 档 频 率 I D F = l o g [ 文 档 总 数 / ( 出 现 过 该 单 词 的 文 档 数 + 1 ) ] 逆文档频率IDF = log[文档总数 / (出现过该单词的文档数+1)] IDF=log[/(+1)]

from sklearn.feature_extraction.text import TfidfVectorizer
vect = TfidfVectorizer() 
_ = vect.fit_transform(X)

文本处理流水线

这次采用Naive bayes,下面是一步步进行的优化过程:

from sklearn.naive_bayes import MultinomialNB # 特征数多时更快
# 设置流水线参数
pipe_params = {'vect__ngram_range':[(1, 1), (1, 2)], 'vect__max_features':[1000, 
10000], 'vect__stop_words':[None, 'english']}

# 实例化流水线
pipe = Pipeline([('vect', CountVectorizer()), ('classify', MultinomialNB())])

# 实例化网格搜索
grid = GridSearchCV(pipe, pipe_params) 
# 拟合网格搜索对象
grid.fit(X, y) 
# 取结果
print(grid.best_score_, grid.best_params_)

进一步调优:加入tfidf,个 FeatureUnion 模块,可以水平(并排)排列特征。

from sklearn.pipeline import FeatureUnion 
# 单独的特征构建器对象
featurizer = FeatureUnion([('tfidf_vect', TfidfVectorizer()), ('count_vect', 
CountVectorizer())])
_ = featurizer.fit_transform(X) 
print(_.shape)     '行数相同,但列数为 2 倍,就是把两个结果concat在一起了'

#设置参数
featurizer.set_params(tfidf_vect__max_features=100, count_vect__ngram_range=(1, 2), count_vect__max_features=300) 
# TfidfVectorizer 只保留 100 个单词,而 CountVectorizer 保留 300 个 1~2 个单词的短语
_ = featurizer.fit_transform(X) 
print(_.shape) 
  • 以下是完成流水线的样子:
pipe_params = {'featurizer__count_vect__ngram_range':[(1, 1), (1, 2)], 
'featurizer__count_vect__max_features':[1000, 10000], 
'featurizer__count_vect__stop_words':[None, 'english'], 
'featurizer__tfidf_vect__ngram_range':[(1, 1), (1, 2)], 
'featurizer__tfidf_vect__max_features':[1000, 10000], 
'featurizer__tfidf_vect__stop_words':[None, 'english']} 
pipe = Pipeline([('featurizer', featurizer), ('classify', MultinomialNB())]) 
grid = GridSearchCV(pipe, pipe_params) 
grid.fit(X, y) 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值