C++实现用户分组--学习

news/2024/11/15 19:00:02/

第一步实现:ETL的设计分三部分:数据抽取(Data Extraction)、数据的清洗转换(Data Transformation)、数据的加载(Data Loading).

构建一个数据容器类,其中包含转换后的MNIST手写数据。还实现了一个数据处理程序,该数据处理程序将提取并转换数据以供将来的算法实现使用。

#ifndef __DATA_H
#define __DATA_H#include <iostream>
#include <vector>
#include "stdint.h"
#include "stdio.h"// 数据类
class data
{std::vector<uint8_t>* feature_vector; // 特征向量uint8_t label; // 标签int enum_label; // 枚举标签 A->1, B->2, C->3, D->4, E->5, F->6, G->7, H->8, I->9, J->10public:data(); // 构造函数~data(); // 析构函数void set_feature_vector(std::vector<uint8_t> *); // 设置特征向量void append_to_feature_vector(uint8_t); // 向特征向量追加数据void set_label(uint8_t); // 设置标签void set_enum_label(int); // 设置枚举标签int get_feature_vector_size(); // 获取特征向量大小uint8_t get_label(); // 获取标签uint8_t get_enumerated_label(); // 获取枚举标签std::vector<uint8_t>* get_feature_vector(); // 获取特征向量};#endif

这段代码定义了一个名为 data 的类,用于处理特征向量和标签。首先,代码使用了头文件保护机制,通过 #ifndef#define 和 #endif 来防止重复包含头文件 data.hpp

在 data 类中,有三个私有成员变量:feature_vector 是一个指向 std::vector<uint8_t> 的指针,用于存储特征向量;label 是一个 uint8_t 类型的变量,用于存储标签;enum_label 是一个整数,用于存储枚举标签,注释中说明了不同字符对应的整数值(例如,A 对应 1,B 对应 2,依此类推)。

在公共成员函数部分,data 类提供了一些方法来操作和访问这些成员变量:

  • void set_feature_vector(std::vector<uint8_t> *):设置特征向量的指针。
  • void append_to_feature_vector(uint8_t):向特征向量中追加一个 uint8_t 类型的值。
  • void set_label(uint8_t):设置标签。
  • void set_enum_label(int):设置枚举标签。

此外,还有一些方法用于获取成员变量的值:

  • int get_feature_vector_size():获取特征向量的大小。
  • uint8_t get_label():获取标签。
  • uint8_t get_enumerated_label():获取枚举标签。
  • std::vector<uint8_t>* get_feature_vector():获取特征向量的指针。

这些方法使得 data 类能够灵活地操作和访问特征向量和标签,适用于需要处理大量数据的场景。

#include "data.hpp"data::data(){feature_vector = new std::vector<uint8_t>;
}data::~data()
{delete feature_vector;
}void data::set_feature_vector(std::vector<uint8_t> *vect)
{feature_vector = vect;
}void data::append_to_feature_vector(uint8_t val)
{feature_vector->push_back(val);
}void data::set_label(uint8_t val)
{label = val;
}void data::set_enum_label(int val)
{enum_label = val;
}int data::get_feature_vector_size()
{return feature_vector->size();
}uint8_t data::get_label()
{return label;
}
uint8_t data::get_enumerated_label()
{return enum_label;
}std::vector<uint8_t>* data::get_feature_vector()
{return feature_vector;
}

这段代码实现了 data 类的构造函数、析构函数以及多个成员函数。首先,代码包含了头文件 data.hpp,以确保类的声明可用。

构造函数 data::data() 初始化了 feature_vector,为其分配了一个新的 std::vector<uint8_t> 对象。析构函数 data::~data() 则负责释放该内存,防止内存泄漏。

new出来的是对象,但是返回的是指向这个对象的指针;

set_feature_vector 方法接受一个指向 std::vector<uint8_t> 的指针,并将其赋值给 feature_vectorappend_to_feature_vector 方法向 feature_vector 中追加一个 uint8_t 类型的值。

set_label 和 set_enum_label 方法分别设置 label 和 enum_label 的值。

get_feature_vector_size 方法返回 feature_vector 的大小。get_label 和 get_enumerated_label 方法分别返回 label 和 enum_label 的值。最后,get_feature_vector 方法返回 feature_vector 的指针。

总体来说,这段代码实现了 data 类的基本功能,使其能够管理和操作特征向量和标签。

2.处理数据

#ifndef __DATA_HANDLER_H
#define __DATA_HANDLER_H#include<fstream>
#include "stdint.h"
#include"data.hpp"
#include<vector>
#include<string>
#include<map>
#include<unordered_set>// 数据处理类
class data_handler
{std::vector<data *> *data_array; // 数据数组std::vector<data *> *training_data; // 训练数据std::vector<data *> *testing_data; // 测试数据std::vector<data *> *validation_data; // 验证数据int num_classes; // 类别数量int feature_vector_size; // 特征向量大小std::map<uint8_t, int> class_map; // 类别映射const double TRAIN_SET_PERCENTAGE = 0.75; // 训练集比例const double TEST_SET_PERCENTAGE = 0.20; // 测试集比例const double VALIDATION_SET_PERCENTAGE = 0.05; // 验证集比例public:data_handler(); // 构造函数~data_handler(); // 析构函数void read_feature_vector(std::string path); // 读取特征向量void read_label_vector(std::string path); // 读取标签向量void split_data(); // 分割数据void count_classes(); // 统计类别数量uint32_t convert_to_little_endian(const unsigned char* bytes); // 转换为小端序std::vector<data *> *get_training_data(); // 获取训练数据std::vector<data *> *get_testing_data(); // 获取测试数据std::vector<data *> *get_validation_data(); // 获取验证数据};#endif

这个类名为 data_handler,用于处理数据集的读取、分割和分类等操作。以下是对该类的详细解释:

成员变量

  1. std::vector<data *> *data_array

    • 指向一个 std::vector 容器的指针,该容器存储了所有的数据对象的指针。
  2. std::vector<data *> *training_data

    • 指向一个 std::vector 容器的指针,该容器存储了训练数据集的数据对象的指针。
  3. std::vector<data *> *testing_data

    • 指向一个 std::vector 容器的指针,该容器存储了测试数据集的数据对象的指针。
  4. std::vector<data *> *validation_data

    • 指向一个 std::vector 容器的指针,该容器存储了验证数据集的数据对象的指针。
  5. int num_classes

    • 存储数据集中类别的数量。
  6. int feature_vector_size

    • 存储特征向量的大小。
  7. std::map<uint8_t, int> class_map

    • 一个映射,用于将类别标签(uint8_t 类型)映射到整数值。
  8. const double TRAIN_SET_PERCENTAGE

    • 常量,表示训练数据集所占的比例,值为 0.75。
  9. const double TEST_SET_PERCENTAGE

    • 常量,表示测试数据集所占的比例,值为 0.20。
  10. const double VALIDATION_SET_PERCENTAGE

    • 常量,表示验证数据集所占的比例,值为 0.05。

构造函数和析构函数

  1. data_handler()

    • 构造函数,用于初始化 data_handler 对象。
  2. ~data_handler()

    • 析构函数,用于释放 data_handler 对象所占用的资源。

成员函数

  1. void read_feature_vector(std::string path)

    • 从指定路径读取特征向量数据。
  2. void read_label_vector(std::string path)

    • 从指定路径读取标签数据。
  3. void split_data()

    • 将数据集分割为训练集、测试集和验证集。
  4. void count_classes()

    • 统计数据集中各个类别的数量。
  5. uint32_t convert_to_little_endian(const unsigned char* bytes)

    • 将字节数组转换为小端格式的 uint32_t 类型。
  6. std::vector<data *> *get_training_data()

    • 返回指向训练数据集的指针。
  7. std::vector<data *> *get_testing_data()

    • 返回指向测试数据集的指针。
  8. std::vector<data *> *get_validation_data()

    • 返回指向验证数据集的指针。
#include <iostream>
#include "data_handler.hpp"data_handler::data_handler()
{data_array = new std::vector<data *>;testing_data = new std::vector<data *>;training_data = new std::vector<data *>;validation_data = new std::vector<data *>;
}
data_handler::~data_handler()
{// Free memory Dynamically allocateddelete data_array;delete testing_data;delete training_data;delete validation_data;
}/*
这段代码的主要目的是为读取图像数据文件的头部信息做好准备。以下是逐行的详细解释:uint32_t header[4]; // |magic number|number of images|number of rows|number of columns|这行代码定义了一个大小为4的无符号整数数组header,用于存储图像数据文件的头部信息。具体的含义是:
magic number:用于标识文件格式的数字,通常用于验证文件类型。
number of images:图像的总数量。
number of rows:每张图像的行数(高度)。
number of columns:每张图像的列数(宽度)。
unsigned char bytes[4];这行代码定义了一个大小为4的无符号字符数组bytes,用于暂时存放从文件中读取的字节,以便后续转换为整型数据。
FILE *f = fopen(path.c_str(), "r");这行代码尝试打开指定路径的文件,并以只读模式("r")打开。如果打开成功,f指针将指向该文件;如果失败,f将为nullptr。path.c_str()用于将std::string类型的path转换为C风格的字符串。
总结
这段代码的主要功能是为从文件中读取和解析图像数据的头部信息做准备。通过定义适当的数据结构来存储所需的头部信息,同时准备打开文件并进行读取操作。这是图像数据处理中的重要一步,因为正确解析头部信息对于后续数据的正确读取和处理至关重要。
*/
void data_handler::read_feature_vector(std::string path)
{uint32_t header[4]; // |magic number|number of images|number of rows|number of columns|unsigned char bytes[4];FILE *f = fopen(path.c_str(), "r");if(f){for(int i=0;i<4;i++){if(fread(bytes,sizeof(bytes),1,f)){header[i] = convert_to_little_endian(bytes);}}printf("Done getting Input File header\n");int image_size = header[2] * header[3];for(int i =0;i<header[1];i++){data *d = new data();uint8_t element[1];for(int j=0;j<image_size;j++){if(fread(element,sizeof(element),1,f)){d->append_to_feature_vector(element[0]);} else{printf("Error reading file\n");exit(1);}}data_array->push_back(d);}printf("Successful read and stored\n");} else{printf("Could not find file\n");exit(1);}
}void data_handler::read_feature_labels(std::string path)
{uint32_t header[2]; // |magic number|number of imagesunsigned char bytes[4];FILE *f = fopen(path.c_str(), "r");if(f){for(int i=0;i<2;++i){if(fread(bytes,sizeof(bytes),1,f)){header[i] = convert_to_little_endian(bytes);}}printf("Done getting Lable File header\n");for(int i =0;i<header[1];++i){uint8_t element[1];if(fread(element,sizeof(element),1,f)){data_array->at(i)->set_label(element[0]);} else{printf("Error reading file\n");exit(1);}}printf("Successful read and stored\n");} else{printf("Could not find file\n");exit(1);}
}
void data_handler::split_data(){std::unordered_set<int> used_indexes;int train_size = data_array->size() * TRAIN_SET_PERCENTAGE;int test_size = data_array->size() * TEST_SET_PERCENTAGE;int validation_size = data_array->size() * VALIDATION_SET_PERCENTAGE;// Training Dataint count = 0;while(count<train_size){int rand_index = rand() % data_array->size();if(used_indexes.find(rand_index)==used_indexes.end()){training_data->push_back(data_array->at(rand_index));used_indexes.insert(rand_index);count++;}}// Testing Datacount = 0;while(count<test_size){int rand_index = rand() % data_array->size();if(used_indexes.find(rand_index)==used_indexes.end()){testing_data->push_back(data_array->at(rand_index));used_indexes.insert(rand_index);count++;}}// Validation Datacount = 0;while(count<validation_size){int rand_index = rand() % data_array->size();if(used_indexes.find(rand_index)==used_indexes.end()){validation_data->push_back(data_array->at(rand_index));used_indexes.insert(rand_index);count++;}}std::cout << "Training data size: " << training_data->size() << "." << std::endl;std::cout << "Test data size: " << testing_data->size() << "." << std::endl;std::cout << "Validation data size: " << validation_data->size() << "." << std::endl;}
void data_handler::count_classes()
{int count = 0;for(unsigned i=0;i<data_array->size();i++){if(class_map.find(data_array->at(i)->get_label())==class_map.end()){class_map[data_array->at(i)->get_label()] = count;data_array->at(i)->set_enum_label(count);count++;} else{data_array->at(i)->set_enum_label(class_map[data_array->at(i)->get_label()]);}}std::cout << "Successfully extracted " << count << " unique classes." << std::endl;
}uint32_t data_handler::convert_to_little_endian(const unsigned char* bytes)
{return (uint32_t) ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | (bytes[3] << 0));
}std::vector<data *> * data_handler::get_training_data()
{return training_data;
}
std::vector<data *> * data_handler::get_testing_data()
{return testing_data;
}
std::vector<data *> * data_handler::get_validation_data()
{return validation_data;
}int main()
{data_handler *dh = new data_handler();dh->read_feature_vector("./train-images-idx3-ubyte");dh->read_feature_labels("./train-labels-idx1-ubyte");dh->split_data();dh->count_classes();
}

总结

data_handler 类提供了一系列方法,用于读取数据、分割数据集、统计类别数量以及获取训练集、测试集和验证集。通过这些方法,可以方便地管理和处理数据集,适用于机器学习和数据分析等场景。

遇到的问题1:abort()作用

回答:

问题2:make clean作用

回答:

简单一点来说就是make clean 就是执行makefile文件里面的clean规则,将编译产生的所有东西清理,项目回到编译之前的状态;

在data_handler.cpp文件里面写了int main(){...} 可以做一个测试,看看对数据的读取有没有出现问题,

于是:g++ -std=c++11 -I.include/  src/* -o main  就是找到头文件,并根据源文件生成可执行文件,实际上就是通过编译data_handler.cpp得到的,当然其中会链接到头文件

Makefile文件更加清晰地展现了 编译链接的过程;

前面都是一些替换符

all下面才是编译的整体流程,可以从下往上面看

CC=g++
INCLUDE_DIR :=${MNIST_ML_ROOT}/include
SRC_DIR  :=${MNIST_ML_ROOT}/src
CFLAGS=-std=c++11 -g
LIB_DATA :=libdata.soall : ${LIB_DATA}${LIB_DATA} : libdir objdir obj/data_handler.o obj/data.o${CC} ${CFLAGS} -o ${MNIST_ML_ROOT}/lib/${LIB_DATA} obj/*.orm -r ${MNIST_ML_ROOT}/objlibdir :mkdir -p ${MNIST_ML_ROOT}/libobjdir :mkdir -p ${MNIST_ML_ROOT}/objobj/data_handler.o : ${SRC_DIR}/data_handler.cpp${CC} -fPIC ${FLAGS} -o obj/data_handler.o -I$(INCLUDE_DIR) -c $(SRC_DIR)/data_handler.cppobj/data.o : ${SRC_DIR}/data.cpp${CC} -fPIC ${FLAGS} -o obj/data.o -I$(INCLUDE_DIR) -c $(SRC_DIR)/data.cppclean :rm -r ${MNIST_ML_ROOT}/librm -r ${MNIST_ML_ROOT}/obj#这里有意思的一个点就是${MNIST_ML_ROOT} 没有被定义,可以在makefile中定义,也可以在运行make命令时指定,如:make MNIST_ML_ROOT=/home/zy/mnist_ml
#也可以 export MNIST_ML_ROOT=/home/zy/mnist_ml,然后在makefile中使用$(MNIST_ML_ROOT)

这段代码是一个 Makefile,主要用于管理和自动化 C++ 项目的构建过程。下面是逐步分解和详细解释:

1. 变量定义
CC=g++:指定 C++ 编译器为 g++。
INCLUDE_DIR :=${MNIST_ML_ROOT}/include:指定头文件的目录,这里的 ${MNIST_ML_ROOT} 需要在执行命令时定义。
SRC_DIR  :=${MNIST_ML_ROOT}/src:指定源代码的目录。
CFLAGS=-std=c++11 -g:指定编译选项,这里使用 C++11 标准并包括调试信息。
LIB_DATA :=libdata.so:指定生成的共享库文件名。
2. 目标与依赖关系
all : ${LIB_DATA}:默认目标是生成共享库 libdata.so。
${LIB_DATA} : libdir objdir obj/data_handler.o obj/data.o:表示生成 libdata.so 依赖于 libdir、objdir 以及两个目标文件 data_handler.o 和 data.o。
3. 目录创建
libdir : 和 objdir ::这两个目标负责创建存放生成文件的目录 lib 和 obj,使用 mkdir -p 确保目录存在。
4. 文件编译
obj/data_handler.o : ${SRC_DIR}/data_handler.cpp:表示目标文件 data_handler.o 依赖于源文件 data_handler.cpp。
obj/data.o : ${SRC_DIR}/data.cpp:表示目标文件 data.o 依赖于源文件 data.cpp。
这两个规则中,使用 g++ 进行编译,选项包括 -fPIC (生成位置无关的代码),-o 指定输出文件,-I$(INCLUDE_DIR) 指定头文件的搜索路径,-c 表示只编译而不链接。
5. 清理操作
clean ::定义了一个清理目标,通过删除 lib 和 obj 目录下的文件来清理构建生成的内容。
总结
这个 Makefile 的主要功能是自动化构建一个 C++ 项目,生成共享库 libdata.so。它通过定义相应的规则和目标,确保在编译源文件之前创建必要的目录,并在构建结束后提供清理功能。重要的是,${MNIST_ML_ROOT} 变量未在 Makefile 中定义,而是可以在命令行中传递,这提高了灵活性。整体来说,这个 Makefile 适合用于处理较为复杂的 C++ 项目构建过程。

递归进行


http://www.ppmy.cn/news/1547256.html

相关文章

HCIP快速生成树 RSTP

STP&#xff08;Spanning Tree Protocol&#xff0c;生成树协议&#xff09;和RSTP&#xff08;Rapid Spanning Tree Protocol&#xff0c;快速生成树协议&#xff09;都是用于在局域网中消除环路的网络协议。 STP&#xff08;生成树协议&#xff09; 基本概念&#xff1a; ST…

python 怎么在模块和文件夹名称不一致的情况下,安装自己的模块

问题 项目的目录如下 root src model resnet.py README.gitignore.gitpyproject.toml 其中 src 是项目实现的模块 假如这个模块的名称叫 my_module 我们想实现 from my_module.model.resnet import Resnet 但是 my_module 源码所在的文件夹名称是 src, 不是 my_module&…

Oracle Instant Client 23.5安装配置完整教程

Oracle Instant Client 23.5安装配置完整教程 简介环境要求安装步骤1. 准备工作目录2. 下载Oracle Instant Client3. 解压Instant Client4. 安装依赖包5. 配置系统环境5.1 配置库文件路径5.2 配置环境变量 6. 配置Oracle钱包&#xff08;可选&#xff09; 验证安装常见问题解决…

javaCV流媒体处理demo

JavaCV 的实现原理 JavaCV 是一个基于 FFmpeg、OpenCV 和其他计算机视觉和多媒体处理库的 Java 封装库&#xff0c;提供了丰富的 API 以供 Java 程序调用这些底层库的功能。JavaCV 的实现原理主要体现在以下几个方面&#xff1a; JNI 封装&#xff1a;JavaCV 通过 Java Nativ…

Java 基础知识

一.泛型编程 1. 泛型的概念和作用是什么&#xff1f; 概念&#xff1a;泛型&#xff08;Generics&#xff09;是在 JDK 5.0 引入的新特性&#xff0c;允许在定义类、接口和方法时使用类型参数。类型参数在使用时被具体的类型替换。作用&#xff1a; 类型安全性&#xff1a;避…

华为机试HJ41 称砝码

首先看一下题 描述 现有n种砝码&#xff0c;重量互不相等&#xff0c;分别为 m1,m2,m3…mn &#xff1b; 每种砝码对应的数量为 x1,x2,x3...xn 。现在要用这些砝码去称物体的重量(放在同一侧)&#xff0c;问能称出多少种不同的重量。 注&#xff1a; 称重重量包括 0 数据范围&a…

【AlphaFold3】开源本地的安装及使用

文章目录 安装安装DockerInstalling Docker on Host启用Rootless Docker 安装 GPU 支持安装 NVIDIA 驱动程序安装 NVIDIA 对 Docker 的支持 获取 AlphaFold 3 源代码获取基因数据库获取模型参数构建将运行 AlphaFold 3 的 Docker 容器 参考 AlphaFold3: https://github.com/goo…

电子电气架构 --- 基于以太网的电子电气架构概述

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所有人的看法和评价都是暂时的&#xff0c;只有自己的经历是伴随一生的&#xff0c;几乎所有的担忧和畏惧…