机器学习实战:这里没有艰深晦涩的数学理论,我们将用简单的案例和大量的示例代码,向大家介绍机器学习的核心概念。我们的目标是教会大家用Python构建机器学习模型,解决现实世界的难题。
本文来自《数据黑客》,登录官网可精彩资讯和文章。数据黑客 - 专注金融大数据的内容聚合和数据聚合平台
机器学习模型要求输入为数值变量,特征必须是大小为(n_samples, n_features)的数值矩阵,目标是(n_samples, 1)的数值向量。但现实世界的数据集有可能包含非数值数据,例如分类变量,文本数据和图像。
这时候需要进行数据预处理(data preprocessing),即采用一些技巧将非数值变量转换为数值变量。
1. 分类变量
分类变量通常分为两种:有序和无序。有序:类别可以相互比较,有大小之分,可以直接转化为数值变量。假设代表收入的变量'income'有3个类别,'low','medium','high',分别代表低收入,中等收入,高收入,类别是可以直接比较的,并且可以排序,'low' < 'medium' < 'high'。
无序:类别不可以比较,需要引入虚拟变量(dummy variable),常用编码技术是独热编码(one-hot encoding)。例如代表性别的变量'gender'有两个类别,'male'和'female',但'male' < 'female'的关系并不成立。
将分类变量转化为数值变量有两种常用方法:标签编码和独热编码。
1.1 标签编码
标签编码(label encoding): 将分类变量的类别编码为数字。
用sklearn.preprocessing.LabelEncoder实现,将包含k个类别的分类变量编码为$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']
1.2 独热编码
独热编码(one hot encoding): 将包含m个类别的分类变量转化为$n*m$的二元矩阵,n是观测值数量,m是类别数量。
假设分类变量'car_type',表示汽车类型,包含类别(BMW, Tesla, Audi),独热编码将产生下图的结果,每一个类别都成为一个新的变量/特征,1表示观测值包含该类别,0表示不包含。
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='
[[0. 1. 0.]
[0. 0. 1.]
[1. 0. 0.]
[0. 1. 0.]
[1. 0. 0.]]
Audi BMW Tesla
0 0.0 1.0 0.0
1 0.0 0.0 1.0
2 1.0 0.0 0.0
3 0.0 1.0 0.0
4 1.0 0.0 0.0
调用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
1.3 虚拟变量陷阱
独热编码会引入K个新特征,分别表示分类变量的K个类别。但如果我们使用回归模型,则不能这么做,因为这会导致多重共线性。
首先我们要理解两个新的概念:虚拟变量陷阱和多重共线性。
虚拟变量陷阱:在回归模型中,虚拟变量(dummy variable)用于表示分类变量的类别,通常用1和0表示。例如性别变量'gender'有两个类别,分别是男性和女性,那么可以引入一个虚拟变量D,如果观测值是男性记为1,否则记为0。如果分类变量有$k$个类别,引入$(k-1)$个虚拟变量,有一个类别作为基准组/参照组。如果引入$k$个虚拟变量,会导致多重共线性。
多重共线性:如果1个自变量可以表示为其它自变量的线性组合,就是完全多重共线性,无法估计回归方程的斜率系数。简单理解就是预测变量(特征)之间高度相关。
假设变量$X$有3个类别,引入3个虚拟变量$X_a, X_b, X_c$,每个变量的取值都是1或0,则必然满足以下关系:$X_a + X_b + X_c = 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.0BMW
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
2. 文本数据
有时候模型的输入是文本数据,例如新闻,社交媒体发言等,需要把文本转化为数值矩阵。
常用处理方法有两种:单词统计(word counts): 统计每个单词出现的次数。
TF-IDF(Term Frequency-Inverse Document Frequency): 统计单词出现的“频率”。
单词统计法的缺陷是当单词出现次数较多,模型会赋予更多的权重,Tfidf可以规避这个缺陷。
2.1 word counts
行代表观测值,列(特征)是所有文档中出现过的单词,特征值表示单词在当前文档中出现的次数。
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
2.2 Tfidf
行代表观测值,列(特征)是所有文档出现过的单词,分数是文档中词的频率乘以所有文档的逆频率,分数越高显示单词在当前文档中使用越多,而在其它文档中使用得越少。
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
数据黑客 - 专注金融大数据的内容聚合和数据聚合平台
我们聚合全网最优秀的资讯和教程:金融大数据
机器学习/深度学习
量化交易
数据工程
编程语言,Python,R,Julia,Scala,SQL
我们提供开源数据接口:下载国内和国外海量金融数据
API接口,将数据整合到您的平台