前面几章我们学习了怎么使用决策树模型对数据建模和做预测。但是前提是,我们已经有一些可以直接喂给模型的数据。从前面的课程中,我们知道,机器学习模型的输入数据要求是固定维度的数值型向量。
那么,实际情况下,我们怎么将我们收集的数据转换为机器学习模型所需要的格式呢?
这时我们就需要对原始数据进行预处理,然后使用特征工程的方法来将这些原始数据转换成机器学习模型所需要的数据格式。
特征工程是指从原始数据中提取和构建有意义、有价值的特征的过程,这些特征能够更好地表示数据的潜在模式和信息,从而提高机器学习模型的性能和预测能力。
特征工程的主要任务包括:
- 特征提取:从原始数据中选择或计算出能够反映数据本质的特征。
- 特征构建:通过对原始数据的组合、变换、聚合等操作创建新的特征。
- 特征选择:从众多特征中筛选出对模型预测最有帮助的特征,去除冗余或无关的特征。
- 特征缩放和归一化:将特征的值调整到合适的范围,以便模型更好地处理和学习。
良好的特征工程可以使模型更容易理解和学习数据中的模式,减少模型的训练时间,提高模型的准确性和泛化能力。它在机器学习和数据分析中起着至关重要的作用,往往需要对数据和业务问题有深入的理解。
接下来我将介绍一些实际的例子加以说明。
图像数据
在计算机中,一张如下图(左图)所示的照片是由很多个像素构成。而每个像素又是由红(R)、绿(G)、蓝(B)三个通道的亮度值组成。亮度值通常在0到255之间,0表示该颜色通道没有强度,255表示最大强度。
比如,上面这张图片长和宽都是38,一共有38×38=1444个像素,每个像素由3个分别代表RGB亮度值的数构成,比如其中一个像素的值是[200,12,12],对应是一种接近红色的颜色。如果你做过前端开发,就会知道可以用RGB三个数值来表示一种色彩。计算机中就是用这种方式来表示一个像素的颜色,进而用很多个这样的3元组来表示一张图片的。
因此,在计算机中,一张图片就是一个三维的数字矩阵。例如,一个分辨率为100×100的RGB图像,可以被表示为一个100×100×3的三维数字矩阵。
从前面的学习可知,我们的机器学习模型需要输入是一个固定维度的向量。
Python中,我们可以使用Pillow库(PIL)或者OpenCV库来读取图像,并将其转换为适合机器学习模型输入的数据格式。
接下来,我们将通过 Python 的 Pillow 库来演示如何从文件中读取图像、将其 resize 成固定尺寸,并转换为机器学习模型所需要的格式。
from PIL import Image
import numpy as np
def process_image(image_path, target_size=(64, 64)):
# 读取图像
image = Image.open(image_path)
# 调整图像尺寸
image = image.resize(target_size)
# 将图像转换为 numpy 数组
image_array = np.array(image)
# 将三维的图像数组展平为一维向量
one_dim_vector = image_array.flatten()
return one_dim_vector
# 示例用法
image_path = 'your_image.jpg'
vector = process_image(image_path)
print(vector.shape)
在上述代码中,我们首先使用 Image.open
函数读取指定路径的图像,然后通过 resize
方法调整其尺寸。接着,将图像转换为 numpy 数组后,使用 flatten
方法将其展平为一维向量。
比如,一张用手机拍摄的图片通常像素高达4000*3000,而我们的模型可能难以直接处理这么大的数据,所以通常需要先resize到一个小的固定大小,比如64*64。这样,不论输入的图像尺寸是多少,最终都可以保证模型输入的向量是一个固定的维度 64*64*3=12288。这样才能满足模型的输入要求。
文本数据
文本数据通常是非结构化的,需要进行一系列的操作将其转化为适合机器学习模型的格式。
一、清洗和预处理
- 去除特殊字符和标点:使用正则表达式或字符串操作去除中文文本中的特殊字符、标点符号等。
- 统一编码:确保文本的编码格式统一,常见的有 UTF-8 。
二、分词
中文文本需要进行分词处理,将句子分割成词语。可以使用一些流行的中文分词库,如 jieba
。
import jieba
text = "这是一个中文文本预处理的示例"
words = jieba.cut(text)
print(list(words))
三、去除停用词
停用词是在文本中出现频率很高但对文本语义贡献不大的词,如“的”“了”“是”等。可以准备一个停用词表来去除这些词。
stopwords = ["的", "了", "是"]
filtered_words = [word for word in words if word not in stopwords]
print(filtered_words)
四、词干提取和词形还原(中文中应用较少)
由于中文的语言特点,词干提取和词形还原在中文处理中应用相对较少。
五、建立词汇表
将处理后的中文词语构建成一个词汇表,为每个词语分配一个唯一的索引。
vocab = set(filtered_words)
vocab_dict = {word: i for i, word in enumerate(vocab)}
print(vocab_dict)
六、向量化
可以使用词袋模型(Bag of Words)或 TF-IDF 等方法将中文文本向量化。
词袋模型是一种将文本表示为向量的简单方法,它忽略了文本中单词的顺序和语法结构,只关注单词的出现次数。
假设我们有以下三个句子:
句子 1:“我喜欢吃苹果”
句子 2:“他不喜欢吃苹果”
句子 3:“今天的苹果很新鲜”
首先,我们创建一个词汇表,包含这三个句子中出现的所有不同单词:[‘我’, ‘喜欢’, ‘吃’, ‘苹果’, ‘他’, ‘不’, ‘今天’, ‘的’, ‘很’, ‘新鲜’]
然后,对于每个句子,我们用一个向量来表示,向量的维度等于词汇表的大小。例如,句子 1 的词袋模型向量为 [1, 1, 1, 1, 0, 0, 0, 0, 0, 0],因为“我”出现 1 次,“喜欢”出现 1 次,“吃”出现 1 次,“苹果”出现 1 次,其他单词未出现。
同理,句子 2 的向量为 [0, 1, 1, 1, 1, 1, 0, 0, 0, 0],句子 3 的向量为 [0, 0, 0, 1, 0, 0, 1, 1, 1, 1]
词袋模型只使用了词出现的次数,但是仅仅计算单词的出现次数,不能区分单词的重要程度。一些常见但对文本语义贡献不大的词(如“的”“了”等)与具有实际意义的关键词被同等对待。因此,就需要用词的重要性来进行加权。这就是TF-IDF(Term Frequency-Inverse Document Frequency)模型。
TF (词频)是指一个单词在某个文档中出现的频率。
例如,在句子“我喜欢吃苹果”中,“苹果”出现了 1 次,句子总词数为 4 ,那么“苹果”的 TF 为 1/4 = 0.25 。
IDF (逆文档频率)是一个衡量单词普遍重要性的指标。假设我们有 10 个文档,其中 5 个文档包含“苹果”这个词,那么“苹果”的 IDF 为 log(10 / 5) = 0.693 。
TF-IDF 就是 TF 乘以 IDF 。如果在另一个文档中,“苹果”出现了 2 次,总词数为 8 ,TF 为 2/8 = 0.25 ,假设同样的文档总数和包含“苹果”的文档数,那么这个文档中“苹果”的 TF-IDF 值为 0.25 * 0.693 = 0.173 。
通过 TF-IDF ,我们可以给在特定文档中频繁出现但在整个文档集合中不太常见的单词赋予更高的权重,从而突出更具代表性的单词。
还是以前面那三个句子为例。下面介绍怎么使用TF-IDF将这三个句子转换成向量表示。
首先,我们来确定词汇表,跟词袋模型一样,词汇表为:[‘我’, ‘喜欢’, ‘吃’, ‘苹果’, ‘他’, ‘不’, ‘今天’, ‘的’, ‘很’, ‘新鲜’]
接下来计算每个单词的 IDF 值。假设我们总共就只有这 3 个句子,“我”出现 1 次,“喜欢”出现 2 次,“吃”出现 2 次,“苹果”出现 3 次,“他”出现 1 次,“不”出现 1 次,“今天”出现 1 次,“的”出现 3 次,“很”出现 1 次,“新鲜”出现 1 次。
那么 IDF 值计算如下:
IDF(“我”) = log(3 / 1) ≈ 1.099
IDF(“喜欢”) = log(3 / 2) ≈ 0.405
IDF(“吃”) = log(3 / 2) ≈ 0.405
IDF(“苹果”) = log(3 / 3) = 0
IDF(“他”) = log(3 / 1) ≈ 1.099
IDF(“不”) = log(3 / 1) ≈ 1.099
IDF(“今天”) = log(3 / 1) ≈ 1.099
IDF(“的”) = log(3 / 3) = 0
IDF(“很”) = log(3 / 1) ≈ 1.099
IDF(“新鲜”) = log(3 / 1) ≈ 1.099
然后计算每个句子中单词的 TF 值,以句子 1 “我喜欢吃苹果”为例:
TF(“我”) = 1 / 4 = 0.25
TF(“喜欢”) = 1 / 4 = 0.25
TF(“吃”) = 1 / 4 = 0.25
TF(“苹果”) = 1 / 4 = 0.25
最后计算 TF-IDF 值,还是以句子 1 为例:
TF-IDF(“我”) = 0.25 * 1.099 ≈ 0.275
TF-IDF(“喜欢”) = 0.25 * 0.405 ≈ 0.101
TF-IDF(“吃”) = 0.25 * 0.405 ≈ 0.101
TF-IDF(“苹果”) = 0.25 * 0 = 0
所以句子 1 的 TF-IDF 向量为 [0.275, 0.101, 0.101, 0, 0, 0, 0, 0, 0, 0]
按照同样的方法,可以计算出句子 2 和句子 3 的 TF-IDF 向量。
这样,通过 TF-IDF 计算,就将每个句子转换为了一个向量。
sklearn软件包提供了 CountVectorizer 和 TfidfVectorizer ,分别使用词袋模型和TF-IDF模型将多个文档或句子转换成向量。下面是简单的示例代码:
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
texts = ["这是第一个中文文本", "这是第二个中文文本"]
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(texts)
print(X.toarray())
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(texts)
print(X.toarray())
通过以上步骤,可以对中文文本数据进行有效的预处理和特征工程,为后续的机器学习或自然语言处理任务做好准备。
统计类数据
统计类数据通常是基于数据的数值属性计算得到的描述性度量,例如均值、中位数、标准差、最大值、最小值、百分位数等。统计类数据在机器学习里也叫统计特征。
统计特征常用的预处理方法如下:
1)缺失值处理:如果统计特征存在缺失值,可以根据数据分布情况选择合适的方法,如用均值、中位数或众数填充。
import pandas as pd
import numpy as np
data = pd.Series([1, 2, np.nan, 4, 5])
# 用均值填充缺失值
data.fillna(data.mean(), inplace=True)
2)异常值处理:通过箱线图、3σ原则等方法检测异常值,可选择删除、修正或单独处理异常值。
import pandas as pd
data = pd.Series([1, 2, 100, 4, 5])
# 假设使用 3σ原则,计算均值和标准差
mean = data.mean()
std = data.std()
# 定义上下限
lower_limit = mean - 3 * std
upper_limit = mean + 3 * std
# 处理异常值
data[data < lower_limit] = lower_limit
data[data > upper_limit] = upper_limit
3)标准化/归一化:将统计特征的值映射到特定的范围,如 [0, 1] 或标准化为均值为 0 、标准差为 1 的分布,常用方法有 Min-Max 缩放和 Z-score 标准化。
from sklearn.preprocessing import MinMaxScaler, StandardScaler
data = np.array([1, 2, 3, 4, 5])
# Min-Max 缩放
scaler_minmax = MinMaxScaler()
scaled_data_minmax = scaler_minmax.fit_transform(data.reshape(-1, 1))
# Z-score 标准化
scaler_standard = StandardScaler()
scaled_data_standard = scaler_standard.fit_transform(data.reshape(-1, 1))
类别特征
类别特征是表示不同类别的离散值,例如性别(男/女)、颜色(红/绿/蓝)等。
类别特征与统计特征不同的点是,他要么是个字符串而不是数字,要么是数字但是数字之间的大小没有意义。比如用户ID是一个类别特征,也是一个数字,但是这个数字的大小没什么意义,两个ID接近的用户可能一点关系都没有。
对这种类别特征,我们为了让机器学习模型能够应用,通常会做编码处理。常见的编码方式有独热编码(One-Hot Encoding)和标签编码(Label Encoding)。独热编码将每个类别转换为一个向量,其中只有对应类别的位置为 1 ,其余为 0 ;标签编码则直接将类别用整数表示。
独热编码:假设我们有一个类别特征“水果”,包含的值为[‘苹果’, ‘香蕉’, ‘橙子’, ‘苹果’, ‘香蕉’]。
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
data = pd.Series(['苹果', '香蕉', '橙子', '苹果', '香蕉'])
encoder = OneHotEncoder(sparse=False)
encoded_data = encoder.fit_transform(data.values.reshape(-1, 1))
print(encoded_data)
# 输出结果类似于:
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]
# [1. 0. 0.]
# [0. 1. 0.]]
在上述示例中,经过独热编码后,“苹果”被编码为[1, 0, 0],“香蕉”被编码为[0, 1, 0],“橙子”被编码为[0, 0, 1]。
标签编码:假设我们有一个类别特征“季节”,包含的值为[‘春季’, ‘夏季’, ‘秋季’, ‘冬季’, ‘春季’]。
import pandas as pd
from sklearn.preprocessing import LabelEncoder
data = pd.Series(['春季', '夏季', '秋季', '冬季', '春季'])
encoder = LabelEncoder()
encoded_data = encoder.fit_transform(data)
print(encoded_data)
# 输出结果类似于:[0 1 2 3 0]
在这个例子中,“春季”被编码为 0,“夏季”被编码为 1,“秋季”被编码为 2,“冬季”被编码为 3。
通过合适的编码处理,可以将类别特征转换为机器学习模型能够处理的数值形式。
对类别特征,我们通常还会做一些特征工程,从已有的特征加工出新的特征。常用的特征工程方法有特征交叉。
特征交叉:对于多个类别特征,可以通过交叉组合创建新的特征,例如将“城市”和“职业”两个类别特征交叉生成新的特征。
import pandas as pd
data1 = pd.Series(['Beijing', 'Shanghai', 'Guangzhou'])
data2 = pd.Series(['Teacher', 'Engineer', 'Doctor'])
data = pd.DataFrame({'City': data1, 'Occupation': data2})
data['City_Occupation'] = data['City'] + '_' + data['Occupation']
特征交叉后,我们可以再使用独热编码将新特征转换成向量。
动手试试
使用前面学到的方法,处理下面的数据,将他们变成可用于机器学习模型建模的数据格式。你可以将下面的数据复制到Excel,并导出为csv文件格式。然后用Python脚本读取,使用前面的特征处理和特征工程方法,将每一条样本都变成一个向量!你可以将「投资倾向」当做建模的标签,然后训练一个决策树模型!注意,第一列的样本编号要去掉!
样本编号 | 年龄(统计) | 收入(统计) | 教育程度(类别) | 婚姻状况(类别) | 职业(类别) | 性别(类别) | 城市(类别) | 购房意向(类别) | 信用评分(统计) | 投资倾向(类别) |
---|---|---|---|---|---|---|---|---|---|---|
1 | 25 | 50000 | 本科 | 未婚 | 工程师 | 男 | 北京 | 有 | 700 | 稳健 |
2 | 30 | 60000 | 硕士 | 已婚 | 教师 | 女 | 上海 | 无 | 750 | 激进 |
3 | 40 | 80000 | 博士 | 离异 | 医生 | 男 | 广州 | 有 | 800 | 平衡 |
4 | 28 | 45000 | 本科 | 未婚 | 程序员 | 女 | 深圳 | 无 | 650 | 稳健 |
5 | 35 | 70000 | 硕士 | 已婚 | 律师 | 男 | 杭州 | 有 | 780 | 激进 |
6 | 50 | 90000 | 本科 | 丧偶 | 经理 | 女 | 成都 | 无 | 850 | 平衡 |
7 | 22 | 30000 | 大专 | 未婚 | 销售 | 男 | 重庆 | 有 | 600 | 稳健 |
8 | 38 | 75000 | 本科 | 已婚 | 会计 | 女 | 武汉 | 无 | 720 | 激进 |
9 | 45 | 85000 | 硕士 | 离异 | 设计师 | 男 | 西安 | 有 | 820 | 平衡 |
10 | 26 | 40000 | 本科 | 未婚 | 文员 | 女 | 长沙 | 无 | 680 | 稳健 |
11 | 32 | 55000 | 本科 | 已婚 | 公务员 | 男 | 南京 | 有 | 730 | 平衡 |
12 | 29 | 48000 | 硕士 | 未婚 | 建筑师 | 女 | 天津 | 无 | 660 | 激进 |
13 | 42 | 78000 | 博士 | 已婚 | 编辑 | 男 | 苏州 | 有 | 790 | 稳健 |
14 | 36 | 65000 | 本科 | 离异 | 摄影师 | 女 | 青岛 | 无 | 710 | 平衡 |
15 | 48 | 88000 | 本科 | 丧偶 | 企业家 | 男 | 大连 | 有 | 840 | 激进 |
16 | 27 | 42000 | 大专 | 未婚 | 客服 | 女 | 沈阳 | 无 | 630 | 稳健 |
17 | 33 | 58000 | 本科 | 已婚 | 运动员 | 男 | 厦门 | 有 | 740 | 平衡 |
18 | 39 | 72000 | 硕士 | 离异 | 记者 | 女 | 济南 | 无 | 700 | 激进 |
19 | 46 | 82000 | 博士 | 已婚 | 艺术家 | 男 | 福州 | 有 | 810 | 稳健 |
20 | 24 | 38000 | 本科 | 未婚 | 护士 | 女 | 合肥 | 无 | 620 | 平衡 |
21 | 37 | 68000 | 本科 | 已婚 | 厨师 | 男 | 佛山 | 有 | 760 | 激进 |
22 | 41 | 76000 | 硕士 | 离异 | 司机 | 女 | 东莞 | 无 | 770 | 稳健 |
23 | 23 | 35000 | 大专 | 未婚 | 导购 | 男 | 宁波 | 有 | 610 | 平衡 |
24 | 34 | 62000 | 本科 | 已婚 | 保安 | 女 | 石家庄 | 无 | 690 | 激进 |
25 | 47 | 86000 | 本科 | 丧偶 | 收银员 | 男 | 长春 | 有 | 830 | 稳健 |
26 | 21 | 32000 | 大专 | 未婚 | 保洁 | 女 | 太原 | 无 | 580 | 平衡 |
27 | 31 | 56000 | 本科 | 已婚 | 发型师 | 男 | 郑州 | 有 | 720 | 激进 |
28 | 44 | 80000 | 硕士 | 离异 | 快递员 | 女 | 昆明 | 无 | 780 | 稳健 |
29 | 20 | 30000 | 大专 | 未婚 | 服务员 | 男 | 贵阳 | 有 | 550 | 平衡 |
30 | 36 | 66000 | 本科 | 已婚 | 幼师 | 女 | 南昌 | 无 | 700 | 激进 |