优秀的编程知识分享平台

网站首页 > 技术文章 正文

如何用不同长度的观测数据对数据集进行分类

nanyue 2024-10-26 11:30:54 技术文章 7 ℃

“经典”机器学习算法通常要求训练数据集采用两个矩阵的格式——一个带样本的矩阵和一个带目标的数组。然而,如何处理观测没有固定长度的数据集呢?

考虑以下情况。您有一个文件数据集,其中每个文件包含一个观察,并且您事先不知道每个文件的长度。不可能将每个文件“flatten”为数字向量,并将这些向量提供给训练算法,因为它们的长度不匹配。我们如何“normalize”观测,使我们能够将它们连接到矩阵中?

在这篇文章中,我将讨论Wrist-Worn Accelerometer数据集(https://archive.ics.uci.edu/ml/datasets/Dataset+for+ADL+Recognition+with+Wrist-worn+Accelerometer),它具有各种长度观测的特征。我将展示如何应用K-Means聚类和随机森林分类器将数据集分类为具有固定长度特征向量的任何其他数据集。

数据集概述

Wrist-Worn Accelerometer数据集代表一组具有3维加速度计测量值的文件。每个文件属于14个类中的一个。每个类代表由加速度计腕带的佩戴者执行的单个动作,例如,步行,饮酒,进食等。总共有839个观测值。

以下直方图显示,通常,每个数据集的文件具有不同数量的记录。例如,我们有26个文件,270个测量值,6个文件,417个记录,依此类推。直方图包括并不代表所有可能的长度,即,存在具有不同观察数量的文件。

我们的目标是将这一堆文件转换为数字矩阵和目标数组,我们可以将其传递到机器学习分类器中。

k - means聚类

为了直观了解K-Means算法如何帮助我们将数据集的观测数据规范化,让我们考虑以下图片:

该图显示了从这个 scikit-learn documentation snippet中获取的手写数字数据的聚类过程的结果。蓝色的点表示数据集的投影到二维空间中使用PCA算法,红色的点在聚类过程中被推断出来。

从图像上可以看出,聚类过程可以被看作是一种原始的连续的连续的平面,形成了一层不均匀的多边形,而这些多边形的数量是由我们指定的,而我们选择的是k -均值算法。

如果我们用最近的(关于欧几里得距离)红点替换每个蓝点,我们就得到了原始点数组的离散表示。因此,不管我们的观测结果如何,它们都被固定大小的质心阵列所取代。

现在情况变得更加清楚了。我们有一个文件(通常)有任意数量的带有观察的行。然后,我们将聚类过程应用到这些观测中。K-Means算法返回一个簇质心数组。我们将这些数组连接到一个固定大小的向量中。从原理上讲,可以用下图来表示该过程:

当我们完成每个文件的过程时,我们将拥有一个具有固定行数和列数的常用数值数据集,并且可以应用任何合适的机器学习算法。

决策树和随机森林

决策树是表示为分析数据集的递归分区的分类器。从数学上讲,它表示一个函数,该函数将属性值的向量作为输入,并返回一个“决策”——一个输出值。树通过对给定的观察属性执行一系列测试来做出决定。树中的每个内部节点都对应于对观察值的一个属性的值的测试,节点的分支被标记为属性的可能值。树中的每个叶节点都指定一个函数要返回的值。

决策树是非常直观的数据分类技术,它“询问”数据集观察组的是 - 否问题,并根据这些“答案”做出决定。但是,这种技术存在一个缺点:决策树对您正在训练它们的数据非常敏感。采用原始数据集的各种训练子集,您可能会获得不同的树,并且它们的准确性可能会有很大差异。此外,单个树预测精度通常不会太高。

如何缓解这些问题呢?其中一个可能的解决方案是构建几棵树并平均预测。这个结果的理论基础来自关于弱学习者的定理。这个想法是,如果你有一个 比一个随机的分类器更好的分类器(比如,在执行二进制分类任务时比均匀硬币要好一点),那么拥有足够数量的这种弱分类器将能够预测类几乎完美准确。

严格地说,为了应用该定理,数据集样本应该是独立且相同分布的随机变量的要求,并且样本来自的分布不应该变化很大。然而,如果每个分类器的训练过程与其他分类器的训练过程略有不同,这种方法在实践中的效果相当好。

将一组弱学习者加入单个强学习者的想法可以应用于任何分类器,而不仅仅应用于决策树。在将这种方法应用于一组树时,强学习者称为随机森林。

现在,当数据集准备就绪并且选择了分类器时,是时候将所有内容连接在一起并构建数据分类管道。

The Pipeline

在使用优秀的scikit-learn库时,构建应该在单个机器上运行的数据分类管道是一个非常简单的过程。唯一需要注意的是我们数据集的特定属性,需要应用预处理技术。

因此,我们只需要读取数据集文件并在库的类之上创建几个thin wrappers。下面的Python代码段显示了如何实现这一点:

"""

Wrist-Worn Accelerometer Dataset using scikit-learn.

"""

import re

from os import listdir

from os.path import exists, join, basename, isdir

import numpy as np

from sklearn.pipeline import make_pipeline

from sklearn.base import TransformerMixin, clone

from sklearn.preprocessing import LabelEncoder, StandardScaler

from sklearn.cluster import KMeans

from sklearn.ensemble import RandomForestClassifier

from sklearn.model_selection import train_test_split

class AccelerometerDatasetReader:

"""

A class intended to read the accelerometer dataset and parse their names

to extract observations classes.

"""

FILE_REGEX = re.compile('^Accelerometer-([\d-]+)-(\w+)-(\w\d+).txt

)

def __init__(self, ignore_model=True):

self.ignore_model = ignore_model

self.samples_ = None

self.targets_ = None

self.encoder_ = None

def read(self, root):

if not exists(root):

raise ValueError(f'path does not exist: {root}')

samples, targets = [], []

for folder in listdir(root):

path = join(root, folder)

if not isdir(path):

continue

is_model = folder.lower().endswith('model')

if self.ignore_model and is_model:

continue

for filename in listdir(path):

match = self.FILE_REGEX.match(basename(filename))

if match is None:

continue

filepath = join(path, filename)

_, category, _ = match.groups()

with open(filepath) as lines:

points = [[

int(value) for value in line.split()]

for line in lines]

samples.append(points)

targets.append(category)

encoder = LabelEncoder()

self.samples_ = samples

self.targets_ = encoder.fit_transform(targets)

self.encoder_ = encoder

@property

def dataset(self):

return self.samples_, self.targets_

class BatchTransformer(BaseEstimator, TransformerMixin):

"""

In our case, each observation is not a 1-dimensional array, but

a matrix, i.e., an array of arrays. Therefore, we can't apply

transformers to it, but we can iterate through matrix rows and

apply a scikit-learn transformer to each of them separately.

"""

def __init__(self, base_transformer):

self.base_transformer = base_transformer

def fit(self, X, y=None):

return self

def transform(self, batch, y=None):

if y is not None:

raise ValueError(

'cannot apply batch transformer in supervised fashion')

transformed = []

for record in batch:

transformer = clone(self.base_transformer)

transformed.append(transformer.fit_transform(record))

return transformed

class KMeansQuantization(BaseEstimator, TransformerMixin):

"""

As in the previous case, we can't apply K-Means directly to the

dataset but need to iterate through samples and concatenate

inferred cluster centroids into 1-dimensional arrays, and then

to concatenate these arrays into a matrix.

"""

def __init__(self, k=5):

self.k = k

def fit(self, X, y=None):

return self

def transform(self, X):

features = []

for record in X:

kmeans = KMeans(n_clusters=self.k)

kmeans.fit_transform(record)

feature_vector = kmeans.cluster_centers_.flatten()

features.append(feature_vector)

return np.array(features)

def main():

root = join('datasets', 'adl')

reader = AccelerometerDatasetReader()

reader.read(root)

X, y = reader.dataset

X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y)

pipeline = make_pipeline(

BatchTransformer(StandardScaler()),

KMeansQuantization(k=3),

RandomForestClassifier(n_estimators=1000))

pipeline.fit(X_train, y_train)

y_preds = pipeline.predict(X_test)

acc = np.mean(y_preds == y_test)

print(f'Dataset accuracy: {acc:2.2%}')

if __name__ == '__main__':

main()

这段代码非常简单明了。AccelerometerDatasetReader是解析数据集文件并创建两个带有观察和目标的容器的类。BatchTransformer类是一个wrapper ,它将scikit-learn transformer应用于每个数据集文件。最后,KMeansQuantization是KMeans估计器上的一个wrapper ,将质心连接成固定大小的特征。

所有这些类构成一个单独的数据分类管道,它接受一个训练数据集,并在一个测试子集上运行预测。我们所获得的测试精度等于58.10%,尽管这个值可以根据所选数据集的分割而变化。

性能优化

我们是否可以使用不同的估计器参数来获得更好的性能,或者找到一个精度相同的更小的模型?为了得到这个问题的答案,我们应该使用交叉验证和grid search。

其思想是选择几个参数的组合,训练分类器,然后比较它们的性能。然后我们只需要选择一个最准确的分数。

在我们的例子中,我们有(至少)三个可能会影响分类器性能的参数:

  • 量化数据时的clusters数量
  • 集合中的树数。
  • 最大允许树的深度

下面的Python代码片段展示了如何结合grid search和scikit-learn pipeline,找到参数的最佳组合:

"""

Main repository: https://github.com/devforfu/Blog/tree/master/trees

"""

import os

from os.path import join

from pprint import pprint

import pandas as pd

from sklearn.pipeline import make_pipeline

from sklearn.preprocessing import StandardScaler

from sklearn.model_selection import GridSearchCV

from sklearn.ensemble import RandomForestClassifier

# imports from local file sitting in working directory

from scikit_learn import AccelerometerDatasetReader

from scikit_learn import BatchTransformer

from scikit_learn import KMeansQuantization

def main():

root = join('datasets', 'adl')

reader = AccelerometerDatasetReader()

reader.read(root)

X, y = reader.dataset

pipeline = make_pipeline(

BatchTransformer(StandardScaler()),

KMeansQuantization(),

RandomForestClassifier())

#define a dictionary that we’re going to use to search for the best classifier

params_grid = {

'kmeansquantization__k': [2, 3, 4],

'randomforestclassifier__n_estimators': [10, 100, 250, 500],

'randomforestclassifier__max_depth': [1, 10, None]}

search = GridSearchCV(pipeline, params_grid, scoring='accuracy', n_jobs=-1, verbose=2)

search.fit(X, y)

cv_results = search.cv_results_

params = pd.DataFrame(cv_results['params']).rename(columns={

'kmeansquantization__k': 'k',

'randomforestclassifier__max_depth': 'max_depth',

'randomforestclassifier__n_estimators': 'n_estimators'

})

params['mean_train_score'] = cv_results['mean_train_score']

params['mean_test_score'] = cv_results['mean_test_score']

best = params.iloc[search.best_index_]

print('Best classifier parameters:')

pprint(best.to_dict())

if __name__ == '__main__':

main()

看看第31-34行。这些行定义了一个字典,我们将用它来搜索最佳分类器。字典键是指变换器和分类器参数,前缀为低位类名,后跟两个下划线。这些值定义了参数搜索域:我们将尝试所有可能的组合,即列表的笛卡尔积。

为了更好地理解参数如何影响分类精度,让我们创建几个解释图。第一个显示最大深度等于无时的分类器精度,这意味着树深度没有严格约束,但取决于分割质量:

如图所示,当仅使用两个clusters训练数据时,分类器实现最佳准确度值。此外,增加集合中的树数通常会提高其性能。

下一个图显示了我们明确限制树深度时的准确度:

这一次分类器的性能越来越差。似乎设置特定的深度限制会阻碍训练过程找到更优的解决方案。

最后,如果我们选择一个极端的情况,尝试用决策残差对数据集进行分类,即最大深度等于1的决策树

上面的图表显示在我们的案例中它不是一个好主意。该模型不够复杂,无法捕获属性及其类之间的所有隐藏依赖关系,并且执行效果不佳。

因此,我们可以得出结论,当选择较少数量的聚类和足够数量的决策树时,我们可以获得更高的准确度。

结论

决策树及其集合是直观清晰且功能强大的机器学习技术。原始算法和一系列优秀的库有许多改进,它们允许以并行方式训练树集合并获得更高的准确性(例如XGBoost),即使是简单的实现也能显示出不错的结果,并证明它是机器学习工程师工具箱中必不可少的算法。

最近发表
标签列表