机器学习实战:这里没有艰深晦涩的数学理论,我们将用简单的案例和大量的示例代码,向大家介绍机器学习的核心概念。我们的目标是教会大家用Python构建机器学习模型,解决现实世界的难题。
机器学习模型要求输入为数值变量,特征必须是大小为(n_samples, n_features)的数值矩阵,目标是(n_samples, 1)的数值向量。但现实世界的数据集有可能包含非数值数据,例如分类变量,文本数据和图像。
这时候需要进行数据预处理(data preprocessing),即采用一些技巧将非数值变量转换为数值变量。
分类变量通常分为两种:有序和无序。
将分类变量转化为数值变量有两种常用方法:标签编码和独热编码。
标签编码(label encoding): 将分类变量的类别编码为数字。
用sklearn.preprocessing.LabelEncoder实现,将包含k个类别的分类变量编码为 0 , 1 , 2 , . . . ( k − 1 ) 0,1,2,...(k-1) 0,1,2,...(k−1)
标签编码一般不用于特征,而是用于目标变量。
假设一个代表性别的特征’gender’,包含两个类别:‘male’,‘female’。标签编码将类别编码为整数,0代表男性,1代表女性。但这不符合模型背后的假设,因为机器学习模型认为数据有算数含义,例如0 < 1,这意味着男性 < 女性,但这种关系不成立。一般会使用独热编码处理分类特征,标签编码仅用于分类目标。
接下来说明如何使用标签编码,假设目标变量代表股票价格趋势,有3种可能的类别,‘up’,‘down’,‘range’.
from sklearn.preprocessing import LabelEncoder
# 目标变量
y = ["up", "up", "down", "range", "up", "down", "range", "range", "down"]
# 创建LabelEncoder对象
le = LabelEncoder()
# 拟合数据
le.fit(y)
# 查看包含哪些类别
print("classes: ", le.classes_)
# 编码为数字
print("encoded labels: ", le.transform(y))
# 调用inverse_transform实现反向操作
print("inverse encoding: ", le.inverse_transform([0, 1, 2]))
classes: ['down' 'range' 'up']
encoded labels: [2 2 0 1 2 0 1 1 0]
inverse encoding: ['down' 'range' 'up']
独热编码(one hot encoding): 将包含m个类别的分类变量转化为 n ∗ m n*m n∗m的二元矩阵,n是观测值数量,m是类别数量。
假设分类变量’car_type’,表示汽车类型,包含类别(BMW, Tesla, Audi),独热编码将产生下图的结果,每一个类别都成为一个新的变量/特征,1表示观测值包含该类别,0表示不包含。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z5sdurJ2-1600075236801)(pictures/one_hot_encoding.png)]
sklearn提供OneHotEncoder类实现独热编码。
import numpy as np
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
# 分类特征
car_types = np.array(["BMW", "Tesla", "Audi", "BMW", "Audi"])
# 创建编码器
oe = OneHotEncoder()
# OneHotEncoder要求输入为二维数组,用reshape重排结构
oe.fit(car_types.reshape(-1, 1))
# 查看类别
print("classes: ", oe.categories_)
# 调用transform获得编码结果
# transform默认返回稀疏矩阵,当分类变量包含很多类别时非常有用,进一步调用toarray可获得熟悉的numpy二维数组
# 创建OneHotEncoder实例时,如果设置sparse=False,调用transform会得到二维数组
encoded_labels = oe.transform(car_types.reshape(-1, 1)).toarray()
print(encoded_labels)
# 用数据框展现最终的结果,便于理解
encoded_labels_df = pd.DataFrame(encoded_labels, columns=oe.categories_)
print(encoded_labels_df)
classes: [array(['Audi', 'BMW', 'Tesla'], dtype='
调用pd.get_dummies实现独热编码,这比sklearn的接口更方便,因为它允许我们直接操作数据框。
pd.get_dummies默认将数据类型为’object’的变量视为分类变量,也可以提供要编码的变量名称。
import pandas as pd
car_types = pd.DataFrame({"car_type": ["BMW", "Tesla", "Audi", "BMW", "Audi"]})
print(car_types)
car_types_encoded = pd.get_dummies(car_types)
print(car_types_encoded)
car_type
0 BMW
1 Tesla
2 Audi
3 BMW
4 Audi
car_type_Audi car_type_BMW car_type_Tesla
0 0 1 0
1 0 0 1
2 1 0 0
3 0 1 0
4 1 0 0
独热编码会引入K个新特征,分别表示分类变量的K个类别。但如果我们使用回归模型,则不能这么做,因为这会导致多重共线性。
首先我们要理解两个新的概念:虚拟变量陷阱和多重共线性。
虚拟变量陷阱:在回归模型中,虚拟变量(dummy variable)用于表示分类变量的类别,通常用1和0表示。例如性别变量’gender’有两个类别,分别是男性和女性,那么可以引入一个虚拟变量D,如果观测值是男性记为1,否则记为0。如果分类变量有 k k k个类别,引入 ( k − 1 ) (k-1) (k−1)个虚拟变量,有一个类别作为基准组/参照组。如果引入 k k k个虚拟变量,会导致多重共线性。
多重共线性:如果1个自变量可以表示为其它自变量的线性组合,就是完全多重共线性,无法估计回归方程的斜率系数。简单理解就是预测变量(特征)之间高度相关。
假设变量 X X X有3个类别,引入3个虚拟变量 X a , X b , X c X_a, X_b, X_c Xa,Xb,Xc,每个变量的取值都是1或0,则必然满足以下关系: X a + X b + X c = 1 X_a + X_b + X_c = 1 Xa+Xb+Xc=1,那么任何一个变量都可以表示为其余两个虚拟变量的线性组合,结果就是完全多重共线性。
独热编码和多重共线性
使用OneHotEncoder创建(k-1)个虚拟变量。
car_types = np.array(["BMW", "Tesla", "Audi", "BMW", "Audi"])
# 将第一个类别作为参照组
oe = OneHotEncoder(drop="first")
# 拟合数据
oe.fit(car_types.reshape(-1, 1))
# 编码
encoded_labels = oe.transform(car_types.reshape(-1, 1)).toarray()
# 查看剔除的特征
# oe.categories_保存了所有的类别,包括剔除的类别
# oe.drop_idx_保存了categories_中被剔除类别的索引
features_drop = oe.categories_[0][oe.drop_idx_.astype(int)]
features_retain = np.delete(oe.categories_[0], oe.drop_idx_.astype(int))
print("features drop: ", features_drop)
print("features retain: ", features_retain)
# 查看结果
encoded_labels_df = pd.DataFrame(encoded_labels, columns=features_retain)
encoded_labels_df["class"] = car_types
print(encoded_labels_df)
features drop: ['Audi']
features retain: ['BMW' 'Tesla']
BMW Tesla class
0 1.0 0.0 BMW
1 0.0 1.0 Tesla
2 0.0 0.0 Audi
3 1.0 0.0 BMW
4 0.0 0.0 Audi
使用pd.get_dummies创建(k-1)个虚拟变量。
car_types = pd.DataFrame({"car_type": ["BMW", "Tesla", "Audi", "BMW", "Audi"]})
# drop_first=True: 将第一个类别作为参照组
car_types_encode = pd.get_dummies(car_types, drop_first=True)
# 查看结果
car_types_join = pd.concat([car_types, car_types_encode], axis=1)
print(car_types_join)
car_type car_type_BMW car_type_Tesla
0 BMW 1 0
1 Tesla 0 1
2 Audi 0 0
3 BMW 1 0
4 Audi 0 0
有时候模型的输入是文本数据,例如新闻,社交媒体发言等,需要把文本转化为数值矩阵。
常用处理方法有两种:
单词统计法的缺陷是当单词出现次数较多,模型会赋予更多的权重,Tfidf可以规避这个缺陷。
行代表观测值,列(特征)是所有文档中出现过的单词,特征值表示单词在当前文档中出现的次数。
from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd
# 准备3个观测值,文本变量
sample = ['problem of evil',
'evil queen',
'horizon problem']
# 创建Vectorizer对象,调用fit_transform方法,返回稀疏矩阵(sparse matrix)
vec = CountVectorizer()
X = vec.fit_transform(sample)
# 将结果转化为数据框,以更直观的方式显示结果
df = pd.DataFrame(X.toarray(), columns=vec.get_feature_names())
df["text"] = sample
df
evil | horizon | of | problem | queen | text | |
---|---|---|---|---|---|---|
0 | 1 | 0 | 1 | 1 | 0 | problem of evil |
1 | 1 | 0 | 0 | 0 | 1 | evil queen |
2 | 0 | 1 | 0 | 1 | 0 | horizon problem |
行代表观测值,列(特征)是所有文档出现过的单词,分数是文档中词的频率乘以所有文档的逆频率,分数越高显示单词在当前文档中使用越多,而在其它文档中使用得越少。
from sklearn.feature_extraction.text import TfidfVectorizer
vec = TfidfVectorizer()
X = vec.fit_transform(sample)
df = pd.DataFrame(X.toarray(), columns=vec.get_feature_names())
df["text"] = sample
df
evil | horizon | of | problem | queen | text | |
---|---|---|---|---|---|---|
0 | 0.517856 | 0.000000 | 0.680919 | 0.517856 | 0.000000 | problem of evil |
1 | 0.605349 | 0.000000 | 0.000000 | 0.000000 | 0.795961 | evil queen |
2 | 0.000000 | 0.795961 | 0.000000 | 0.605349 | 0.000000 | horizon problem |
如果喜欢我们的文章,记得点赞和收藏哦,我们每天都会为大家带来Python,数据科学和量化交易的精品内容。
【关于我们】
蜂鸟数据:国内领先的金融数据API提供商。
蜂鸟数据团队由业界顶尖的数据工程师,数据科学家和宽客组成,我们正努力构建一个强大的金融数据库,并提供API接口,目标是令金融数据开源化和平民化。
浏览并测试我们接口吧,目前覆盖股票,外汇,商品期货,数字货币和宏观经济领域,包括实时报价(tick)和历史数据(分钟),提供REST API和Websocket两种接入方式,能够满足金融分析师,量化交易和理财app的需求。
需要金融数据?利用蜂鸟API将数据整合到您的应用
如果您准备好了,请登录蜂鸟官网,注册免费获取API密钥,然后开始探索我们的金融数据库吧。