填充分类特征
定性填充
- 对于定性的列,我们可以计算最常见的类别用于填充
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)+(w2−w1)x2+b=(w2−w1)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)
流水线处理
前两大节中,我们做了一些数据上的处理转换,现在理一下思路,然后做成流水线。
- 使用imputer填充定性与定量的数据
- 对boolean和city列做虚拟变量编码
- 对ordinal column列进行标签编码
- 对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)