遗漏的部分

This commit is contained in:
qing committed 2026-08-02 21:21:03 +08:00
1 parent 84fb370da8
commit 0493383ee8
63 files changed
+28237 -4

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
/*
* Area是某个形状上的一个区域,它没有自己的帧缓冲区
*
*
*/
#pragma once
#include "Drawable.h"
namespace qing {
class Area: public Drawable {
public:
/* Phase Plotting Method 相图法 */
Area(Drawable *d, int w, int h, int x, int y, int rotate, int fontsize);
Color _get(int x, int y) override; /* 实现接口 */
void _p(int x, int y, Color &clr) override;
virtual void flush(Drawable *n, bool border = false) override;
virtual void move(int x, int y);
protected:
Drawable *d; /* 持有的绘制主体 */
};
}
+72
View File
@@ -0,0 +1,72 @@
#pragma once
#include <iostream>
#include <map>
namespace qing {
/*
* C++ 的命令行
*/
class Arguments {
public:
Arguments(int argc, char **argv) {
for (int i=1; i<argc; ++i) {
std::string item = argv[i];
if (item.length() > 1 && item[0] == '-' && item[1] != '-') {
for (unsigned long j=1; j<item.length(); ++j) {
if (item.length() == 2 && i < argc-1 && argv[i+1][0] != '-') {
std::string args = "";
while (i < argc-1 && argv[i+1][0] != '-') {
args += (args == "") ? "" : " ";
args += std::string(argv[++i]);
}
mc.insert(std::pair(item[j], args));
std::cout << item[j] << "=" << args << std::endl;
} else {
mc.insert(std::pair(item[j], ""));
std::cout << item[j] << std::endl;
}
}
} else if (item.length() > 2 && item[0] == '-' && item[1] == '-' && item[2] != '-') {
std::string arg = get_long_param_key(item);
size_t pos =arg.find('=');
if (pos == (long unsigned int)-1) {
std::cout << arg << std::endl;
m.insert(std::pair<std::string, std::string>(arg, ""));
} else {
std::string k = arg.substr(0, pos);
std::string v = arg.substr(pos+1, arg.length()-pos-1);
std::cout << k << "=" << v << std::endl;
m.insert(std::pair<std::string, std::string>(k, v));
}
} else {
throw WrongArgument(item);
}
}
}
std::map<std::string, std::string> &get_m() {
return m;
}
std::map<char, std::string> &get_mc() {
return mc;
}
class WrongArgument: std::runtime_error { /* 错的参数 */
public:
WrongArgument(std::string key): std::runtime_error(key) {}
};
private:
std::map<std::string, std::string> m;
std::map<char, std::string> mc;
std::string get_long_param_key(std::string &s) {
return s.substr(2, s.length() -2);
}
};
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "lci/Shape.h"
#include "lci/字体绘制器.h"
namespace qing {
class CharShape: public Shape {
public:
CharShape(int w, int h, int x, int y, wchar_t wc)
: Shape(w, h, x, y), wc(wc), 绘制(w, h) {}
virtual void Draw(Drawable &d, Color clr) override{
int w = getw();
int h = geth();
int x = getx();
int y = gety();
绘制.drawvfont(&d, x, y, wc, clr);
}
private:
wchar_t wc;
字体绘制器 绘制;
};
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include "Area.h"
#include "InputBox.h"
#include <memory>
#include <mutex>
namespace qing {
/*
* CmdArea(命令区域)是一种特殊的Area区域
*
* 基层绘制方法我们已经在Area中实现了
*/
class CmdArea: public Area {
public:
CmdArea(Drawable *d, int w, int h, int x, int y, int rotate =0, int fontsize=18, bool hasBox = false);
void Input(char c);
void ClearInput();
virtual void print(char *utf8, int d=0); /* FIXME: d不知道是什么*/
void update_input();
void delete_input();
void clearBox();
std::string get_input_and_clear();
private:
int x_f=0, y_f=0;
std::unique_ptr<InputBox> area;
std::mutex lock;
std::string input = "";
void handle_ansi_code(char *buffer);
bool hasBox;
};
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
namespace qing {
/* 32位色彩和16位色彩 */
class Color{
public:
unsigned char r=0,g=0,b=0;
Color(unsigned char r=0, unsigned char g=0, unsigned char b=0) {
this->r=r;
this->g=g;
this->b=b;
}
};
extern const Color black;
extern const Color white;
extern const Color green;
extern const Color blue;
extern const Color red;
/* FIXME: 完成这个类定义 */
class Color16 {
public:
};
}
+63
View File
@@ -0,0 +1,63 @@
#pragma once
#include <iostream>
#include <stdexcept>
#include <thread>
#include "fsm.h"
#include "ThreadInterface.h"
/*
@qing:包含一个简易的多线程控制机制。
需要共享内存的变量和函数声明为全局变量(通常需要带锁),
线程中单独使用的变量和函数声明到属于它的命名空间中。
每个线程的主函数包括了对其的流程控制代码。
stop,setup,loop,clean函数中填入每个状态下的执行代码。
其中需要用到的函数,又实现在后边。
*/
namespace qing {
namespace lci{
//线程的主函数
//-----------------------------------------------------------
/*通常情况下,线程的主函数。*/
//前向声明脚本类型
class CommonThread: public lci::Fsm, ThreadInterface {
//一个持有资源线程的状态机类型
public:
//构造函数.
//没有必要输入打印机、脚本、线程的名字,放到全局变量
CommonThread(std::string name);
//暂时删除复制构造函数
CommonThread(CommonThread&) = delete;
//如果把析构函数放在这里,将会导致纯虚函数错误
virtual ~CommonThread();
//对线程名字的设置
void SetLabel(std::string name);
//对线程名字的获取
std::string& GetLabel();
//-----------------------------------------------------------
//等待线程启动完毕
//线程启动失败返回失败。线程启动成功或者现程结束返回成功。
//
//这个函数在某些情况下有可能会一直等待,直到循环结束
bool WaitStart(unsigned long n);
void WaitClose();
virtual void StopEvent() override;
virtual void WakeEvent() override;
virtual void LoopEvent() override;
virtual void ClearEvent() override;
void thread_main();
private:
//线程的名字
std::string label;
//用来存放该子线程的标识符。
std::thread *thread = NULL;
};
}
}
+89
View File
@@ -0,0 +1,89 @@
/*
* 这个是个抽象类,子类实现抽象的内部接口后
*
* 可以调用它已经写好的绘制方法。
*/
#pragma once
#include "Rectangle.h"
#include "Color.h"
#include <iostream>
/* 用于文字的绘制
* FIXME: 使用类封装 */
extern unsigned char ttu[];
extern int ttunumber;
extern unsigned char zzhi[];
extern int zzhinumber;
namespace qing {
class Drawable {
public:
/* 表示旋转方向 */
enum class Rotate: char {
R_0 = 0,
R_90 = 1,
R_180 = 2,
R_270 = 3
};
/* 非法的旋转模式 */
class InvalidRotateMode: std::runtime_error {
public:
InvalidRotateMode(): std::runtime_error("Invalid rotate mode.") {}
};
/*====== attr 属性区(访问器) ======*/
Drawable(int w, int h, int x, int y, int rotate, int fontsize);
virtual Rectangle get_size();
virtual Rectangle get_pos();
virtual void set_pos(Rectangle rect);
virtual Color get_color(int x, int y);
virtual void set_color(Color &color);
void FontSize(int fontsize);
int FontSize_get();
virtual Color& getFontsColor();
/*------ basic 基本 ------
* 这些类需要由子类分别进行实现,但是子类不直接调用,
* 而是由抽象基类中实现的方法调用 */
virtual Color _get(int x, int y) = 0; /* 获取像素点像素 */
virtual void _p(int x, int y, Color &clr) = 0; /* 绘制像素点像素 */
//virtual void _up(int x, int y, int w, int h, int level) = 0; /* FIXME: 不使用屏幕移动函数 */
//virtual void _down(int x, int y, int w, int h, int level) = 0;
//virtual void _left(int x, int y, int w, int h, int level) = 0;
//virtual void _right(int x, int y, int w, int h, int level) = 0;
//virtual void _clear(int x, int y, int w, int h) = 0; /* FIXME: 实现该方法 */
/*====== drawer 绘制 ======
* 提供给子类和外部的绘制方法 */
virtual void point(int x, int y, Color &clr);
virtual void line(int x1, int y1, int x2, int y2, Color &clr);
virtual void rectangle(int x, int y, int w, int h, Color &clr);
virtual void rectangle_fill(int x, int y, int w, int h, Color &clr);
virtual void flush(Drawable *n, bool border) = 0;
//virtual void clear(); /* 对于区域来说,清理可能较为复杂 */
/*------ printer 打印 ------
* 打印文字
*
* 只实现了打印文字的基本方法 */
void drawvfont(int x, int y, unsigned short z, Color &clr);
/*------ test 测试 ------
* 绘制测试图案 */
virtual void test(); /* 颜色测试 */
protected:
/*------ window ------
* 窗口属性 */
int w, h, x, y, rotate=0;
/*------ fonts lib ------
* 文字库 */
int font_w, font_h;
unsigned short *chss = (unsigned short *)zzhi;
unsigned char *vec = ttu;
int zn;
int vennum;
/* 绘制文字 */
unsigned char *drawvect(int x, int y, unsigned char *vect, int width, int high, Color &clr);
/*------ printer ------*/
Color clr = green;
//Color clr = white;
};
}
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include "Rectangle.h"
#include "Color.h"
#include "Drawable.h"
#include <iostream>
#include <linux/fb.h>
namespace qing{
/* FIXME: 这个类的绘制点方法可能过于复杂 */
class FrameBuf: public Drawable { /* 帧缓冲区类 */
public:
FrameBuf(const char *path, int rotate, int fontsize);
FrameBuf(const FrameBuf&) = delete; /* 持有fd */
~FrameBuf();
int get_bpp(); /* bits_per_pixel 像素比特 */
virtual Color _get(int x, int y) override;
virtual void _p(int x, int y, Color &color) override;
virtual void flush(Drawable *d, bool border = false) override;
void clear(); /* FIXME: 也许把这个方法加到抽象父类中? */
class UnknowBPP: std::runtime_error { /* 未知标准的像素比特 */
public:
UnknowBPP(): std::runtime_error("Unknow the number of Bits Per Pixel.") {}
};
static const int _HEAD_LEN = 8;
private:
int d = -1;
struct fb_var_screeninfo var_info; /* 可变参数 */
struct fb_fix_screeninfo fix_info; /* 固定参数 */
int x_over = 0; /* 横轴过量 */
int line_jump = 0; /* 行跳 */
char *buf = nullptr; /* 缓冲区指针 */
/* 根据像素比特的不同分情况讨论 */
Color get16(__u32 x, __u32 y);
Color get32(__u32 x, __u32 y);
void p32(__u32 x, __u32 y, Color &color);
void p16(__u32 x, __u32 y, Color &color);
};
}
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include "Area.h"
namespace qing {
class InputBox: public Area { /* 输入框 */
public:
InputBox(Drawable *d, int w, int h, int x, int y, int rotate, int fontsize);
virtual void print(char *utf8, int line);
};
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <iostream>
#include <string>
#include <map>
#include <linux/input.h>
//#define MAX 64 /* FIXME: 这个是不是没用了 */
namespace qing {
class KeyboardEvent{
public:
KeyboardEvent();
~KeyboardEvent();
struct input_event get(); /* 获取键盘事件 */
class NoEvent: std::runtime_error{ /* 键盘无事件异常 */
public:
NoEvent(std::string s): std::runtime_error(s){}
};
private:
std::map<std::string, int> pool;
static bool is_event_with_number(const char *filename);
static void set_noblocking(int d);
int maxd();
static void print(struct input_event ev);
};
}
+53
View File
@@ -0,0 +1,53 @@
/*
* 控件池区域
*
* 能够容纳别的控件,不需要重写flush()方法
*/
#include <memory>
#include <unordered_map>
#include "lci/Area.h"
#include "lci/Drawable.h"
namespace qing {
class PoolArea: public Area{
public:
PoolArea(Drawable *d, int w, int h, int x, int y, int rotate, int fontsize)
:Area(d, w, h, x, y, rotate, fontsize) {};
void add(const std::string& name, const std::shared_ptr<Area>& item) {
umap.insert({name, item});
}
/* 访问控件 */
std::shared_ptr<Area>& operator[](const std::string& key) {
return umap.at(key);
}
/* 访问控件 - const */
const std::shared_ptr<Area>& operator[](const std::string& key) const {
return umap.at(key);
}
/* 安全访问 - 不抛异常 */
//std::shard_ptr<Area> get(const std::string& key) const {}
/* 检查是否存在 */
bool contains(const std::string& key) const {
return umap.find(key) != umap.end();
}
/* 获取控件数量 */
size_t size() const { return umap.size(); }
/* 迭代器支持 - 范围for循环 */
std::unordered_map<std::string, std::shared_ptr<Area>>::iterator begin() { return umap.begin(); }
std::unordered_map<std::string, std::shared_ptr<Area>>::iterator end() { return umap.end(); }
/* 迭代器支持 - const版本 */
std::unordered_map<std::string, std::shared_ptr<Area>>::const_iterator begin() const { return umap.begin(); }
std::unordered_map<std::string, std::shared_ptr<Area>>::const_iterator end() const { return umap.end(); }
/* FIXME: cbegin/cend 支持 */
private:
std::unordered_map<std::string, std::shared_ptr<Area>> umap;
};
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
namespace qing{
class Rectangle{ /* 矩形类 */
public:
int w=0; /* width */
int h=0; /* height */
int b=0; /* ? */
Rectangle(int w=0, int h=0, int b=0) {
this->w=w;
this->h=h;
this->b=b;
}
};
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include "lci/Drawable.h"
#include "lci/Color.h"
namespace qing {
/* 【形状】
* 抽象基类 */
class Shape {
public:
Shape(int w, int h, int x, int y)
: w(w), h(h), x(x), y(y) {}
virtual void Draw(Drawable &d, Color clr) = 0;
int getw() {
return w;
}
void setw(int w) {
this->w = w;
}
int geth() {
return h;
}
void seth(int h) {
this->h = h;
}
int getx() {
return x;
}
void setx(int x) {
this->x = x;
}
int gety() {
return y;
}
void sety(int y) {
this->y = y;
}
private:
int w, h;
int x, y;
};
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
/*
* 形状区域
*/
#include "lci/Area.h"
#include "lci/Shape.h"
#include "vector"
#include "memory"
namespace qing {
class ShapeArea: public Area {
public:
ShapeArea(Drawable *d, int w, int h, int x, int y, int rotate, int fontsize)
: Area(d, w, h, x, y, rotate, fontsize){}
size_t size() const { return list.size(); }
void add(const std::shared_ptr<Shape>& item) {
list.push_back(item);
}
Shape& operator[](const size_t idx) {
return *(list[idx]);
}
const Shape& operator[](const size_t idx) const {
return *(list[idx]);
}
std::vector<std::shared_ptr<Shape>>::iterator begin() { return list.begin(); }
std::vector<std::shared_ptr<Shape>>::iterator end() { return list.end(); }
std::vector<std::shared_ptr<Shape>>::const_iterator begin() const { return list.begin(); }
std::vector<std::shared_ptr<Shape>>::const_iterator end() const { return list.end(); }
private:
std::vector<std::shared_ptr<Shape>> list;
};
}
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include <iostream>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
namespace qing {
class Term {
public:
/* 需要输入当前的控制台号(源头tty) */
Term(int target_tty, int origin_tty) {
this->origin_tty = origin_tty;
chvt(target_tty);
HideCursor(target_tty);
}
~Term(){ /* 切回源tty */
chvt(origin_tty);
}
private:
int origin_tty;
static int open_tty(int num) {
char path[16] = {'\0'};
snprintf(path, sizeof(path), "/dev/tty%d", num);
int d = open(path, O_WRONLY);
if (d == -1)
throw std::runtime_error(path);
else
return d;
}
static void HideCursor(int id) {
int d = open_tty(id);
const char *hide = "\033[?25l";
if (write(d, hide, strlen(hide)) == -1){
close(d);
throw std::runtime_error("term writting");
}
close(d);
}
static void chvt(int id) {
char cmd[16] = {'\0'};
snprintf(cmd, sizeof(cmd), "chvt %d", id);
int err = system(cmd);
if (err)
throw std::runtime_error("chvt");
}
};
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
namespace qing {
class ThreadInterface {
public:
//---------------------------------------------
//程序睡眠阶段的操作函数。
virtual void StopEvent() = 0;
//程序设置阶段的操作函数。
virtual void WakeEvent() = 0;
//程序运行阶段的操作函数。
virtual void LoopEvent() = 0;
//程序清理阶段的操作函数。
virtual void ClearEvent() = 0;
//可以将操作函数作为一种事件,可以从外界传入写好的事件函数,
//那么就需要将操作函数设定为静态函数
};
}
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include <iostream>
#include <cstddef>
namespace qing {
/* ANSI是WINDOWS操作系统默认的编码格式。 */
class ansi{
public:
static int size_gb18030(unsigned char *c, size_t maxLen);
static int size_gb2312(unsigned char first_byte);
class WrongLength: public std::runtime_error { /* 非法的长度 */
public:
WrongLength(): std::runtime_error("") {}
};
};
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include<mutex>
#include<condition_variable>
namespace qing{
namespace lci{
//用来存储状态的枚举类型
enum Stat{
SSHUT = -1,
SSTOP = 0,
SSTART = 1,
SRUNNING = 2
};//Status
class Fsm {//FiniteStateMachine有限状态机
public:
//构造函数
Fsm();
//检查状态
enum Stat chk();
//改变状态
void wake();
void stop();
void shut();
void run();
//protected:
//等待事件唤醒
void suspend();
private:
//锁类型,确保状态机线程安全
std::mutex lock;
//条件变量,用来执行等待或者唤醒
std::condition_variable cv;
//存放状态机的状态
enum Stat stat;
};//StateMachine
}//lci
}//qing
+160
View File
@@ -0,0 +1,160 @@
#pragma once
#include "Area.h"
#include "CmdArea.h"
#include "CommonThread.h"
namespace qing {
namespace lci{
/* PrintThread 打印线程 */
class p_th: public CommonThread {
public:
/**
* 构造函数 传入要读取的控制台设备文件描述符,背景画布,以及命令行区域
*/
p_th(std::string name, int masterfd, Drawable *back, CmdArea *font)
: CommonThread(name) {
this->masterfd = masterfd;
this->back = back;
this->font = font;
}
/**
* 析构函数 关闭线程
*
* 我在可控制线程中主要遇到的问题便是线程的自主终止,
*
* 在 Python 中似乎是只能实现可控制线程的手动退出的,
*
* 我在想是不是在cpp中也做成那样。
*
*
* FIXME(20260108): 啥意思不懂
*/
~p_th(){
/* 使线程自主关闭 */
if (this->chk() != lci::SSHUT){
this->shut();
}
/* 等待线程退出 */
this->WaitClose();
}
/**
* 暂时删除复制构造函数,因为复制出一个与原来线程无关的空白线程似乎没有什么作用
*
* 如果让两个变量引用同一个线程,会使得它们在结束的时候出现错误。
*/
p_th(p_th&) = delete;
/**
* 线程睡眠阶段的操作函数,线程等待。
*
* suspend() 方法似乎不太稳定,我还得再测试测试。
*/
void StopEvent() override {
this->suspend();
}
/**
* 线程苏醒之后执行的操作,线程启动。
*
* 我不知道为什么要等待1秒。
*/
void WakeEvent() {
/* 将文件描述符集合清零 */
FD_ZERO(&read_fds);
/* 清空缓存字符串 */
output = "";
/* 设置完毕,线程进入运行态 */
this->run();
}
/**
* 线程运行阶段的循环体,线程运行。
*
* 使用 SELECT 进行缓冲区的探测。
*/
void LoopEvent() {
/* 对可读集合进行清零 */
//FD_ZERO(&read_fds);
/* 设置可读集合 */
//FD_SET(masterfd, &read_fds);
/**
* 仅在集合中不存在控制台描述符时,才往其中添加该描述符
*
* 在这个集合中,应该只会存在这一个文件描述符吧
*/
if (!FD_ISSET(masterfd, &read_fds))
FD_SET(masterfd, &read_fds);
/* 设置超时时间 */
timeout.tv_sec = 0;
timeout.tv_usec = 10000;
int ret = select(masterfd + 1, &read_fds, NULL, NULL, &timeout);
if (ret == -1) {
//throw std::runtime_error("select");
//this->stop();
return; /* qing(20260108): 为什么不停止 */
} else if (ret == 0) { /* No data to read 无值可读 */
if (output == "") return; /* 在空闲时进行打印*/
font->print((char*)output.c_str(), 0); /* 按次为单位进行刷新应该可以提高效率 20260108(qing): ?*/
back->flush(font, false);
output = "";
} else {
if (FD_ISSET(masterfd, &read_fds)) { /* 有值可读 */
nread = read(masterfd, buffer, sizeof(buffer));
if (nread > 0) {
buffer[nread] = '\0';
output += buffer;
}
}
}
}
/**
* 线程执行过后的清理函数,清理资源。
*
* 什么也不做。我们在唤醒时并未申请资源。
*/
void ClearEvent() {
;
}
private:
int masterfd; /* 对象中存储的要读取的控制台设备文件描述符。 */
Drawable *back; /* 对象中存储的背景画布指针 */
CmdArea *font; /* 对象中存储的命令行区域指针 */
std::string output; /* 从终端设备中读取的进程输出 */
//--------------------------------------------
/* 下面的变量是为了提高速度,避免重复申请 */
fd_set read_fds; /* 文件描述符集合,用于存储 SELECT 返回的可读区域 */
struct timeval timeout; /* 时间结构体 用于传入 SELECT 函数的超时时间 */
ssize_t nread; /* 这一次从控制台中所读取大小 */
char buffer[1024]; /* 存储从控制台中读取字符的缓冲区,一次最多读这么多 */
};
}
}
+8
View File
@@ -0,0 +1,8 @@
#pragma once
namespace qing {
class utf8{ /* */
public:
static int size(const unsigned char c);
static char *to_ansi(char *s);
};
}
+114
View File
@@ -0,0 +1,114 @@
/* 图层区域是能够绘制图片的区域类型 */
#define cimg_display 0
#define cimg_use_jpeg
#define cimg_use_png
#include <CImg.h>
#include "cn/中文化.hpp"
#include "lci/Area.h"
使用 命名空间 cimg_library;
命名空间 qing {
类 图层区域: 公开的 Area { /* 图层区域 */
公开的:
图层区域(Drawable *d, int w, int h, int x, int y, int rotate, int fontsize, CImg<unsigned char>& img)
: Area(d, w, h, x, y, rotate, fontsize) {
/* 储存图片,需要调用图片的构造函数 */
this->img = img;
}
空类型 涂写图像() {
单精度小数 区域长宽比 = (float)w / h;
单精度小数 图片长宽比 = (float)img.width() / img.height();
/* 长宽比越大,代表图片越横向细长
* 长宽比越小,代表图片越竖向细长
* 长宽比为1时,图片为正方形
*
* 图片长宽比较大时,取区域长
* 图片长宽比较小时,取区域高 */
逻辑型 长为标准 = 图片长宽比 > 区域长宽比;
如果(长为标准) {
auto fixed = scaleByWidth(this->img, this->w);
CImg<unsigned char> gray = 转灰度图(fixed);
auto start = (int)(this->h - gray.height()) / 2;
for (int y = 0; y < gray.height(); ++y) {
for (int x = 0; x < gray.width(); ++x){
auto xx = x;
auto yy = y + start;
auto clr1 = 颜色转换(gray(x, y), this->clr);
this->point(xx, yy, clr1);
}
}
}
否则{ /* 以宽为标准 */
auto fixed = scaleByHeight(this->img, this->h);
auto gray = 转灰度图(fixed);
auto start = (int)(this->w - gray.width()) / 2;
for (int y = 0; y < gray.height(); ++y) {
for (int x = 0; x < gray.width(); ++x){
auto xx = x + start;
auto yy = y;
auto clr1 = 颜色转换(gray(x, y), this->clr);
this->point(xx, yy, clr1);
}
}
}
}
私有的:
CImg<unsigned char> img;
/* 以长为标准缩放 */
static CImg<unsigned char> scaleByWidth(const CImg<unsigned char>& src, int newWidth) {
/* 计算缩放比例 */
float ratio = (float)newWidth / src.width();
int newHeight = (int)(src.height() * ratio);
/* 使用_resize方法进行缩放 */
return src.get_resize(newWidth, newHeight, -100, -100, 5);
}
/* 以高为标准缩放 */
static CImg<unsigned char> scaleByHeight(const CImg<unsigned char>& src, int newHeight) {
/* 计算缩放比例 */
float ratio = (float)newHeight / src.height();
int newWidth = (int)(src.width() * ratio);
/* 计算_resize方法进行缩放 */
return src.get_resize(newWidth, newHeight, -100, -100, 5);
}
/* 将灰度颜色转换为我们目前画笔的颜色 */
静态的 Color 颜色转换(无符号 字符 灰度, Color clr) {
单精度小数 红 = (单精度小数)clr.r / 255;
单精度小数 绿 = (单精度小数)clr.g / 255;
单精度小数 蓝 = (单精度小数)clr.b / 255;
自动的 结果 = Color(红* 灰度*255, 绿*灰度*255, 蓝*灰度*255);
返回 结果;
}
/* 将CImg对象转为灰度图 */
静态的 CImg<unsigned char> 转灰度图(CImg<unsigned char>& img) {
if (img.spectrum() == 3) {
return img.get_normalize(0, 255).get_RGBtoXYZ().get_channel(1);
}
else if (img.spectrum() ==4) {
auto rgb_img = t_4_to_3(img);
return rgb_img.get_RGBtoXYZ().get_channel(1);
}
else {
抛出异常 std::runtime_error("没有对应通道数的算法");
}
}
/* 将4通道图片转为3通道 */
静态的 CImg<unsigned char> t_4_to_3(CImg<unsigned char>& img) {
// 创建RGB图像(3通道)
CImg<unsigned char> rgb_img(img.width(), img.height(), 1, 3);
cimg_forXY(rgb_img, x, y) {
rgb_img(x, y, 0, 0) = img(x, y, 0, 0); // R
rgb_img(x, y, 0, 1) = img(x, y, 0, 1); // G
rgb_img(x, y, 0, 2) = img(x, y, 0, 2); // B
// 忽略alpha通道 img(x, y, 0, 3)
}
return rgb_img;
}
};
}
+121
View File
@@ -0,0 +1,121 @@
/*
* 用于在可绘制对象上绘制中文文字
*
* 绘制时将对象作为参数传入
*/
#include "cn/中文化.hpp"
#include "lci/Drawable.h"
/* 用于文字的绘制,只要不动就线程安全 */
extern unsigned char ttu[];
extern int ttunumber;
extern unsigned char zzhi[];
extern int zzhinumber;
命名空间 qing {
类 字体绘制器 {
私有的:
/*------ fonts lib ------
* 文字库 */
int font_w, font_h;
unsigned short *chss = (unsigned short *)zzhi;
unsigned char *vec = ttu;
int zn = zzhinumber;
int vennum = ttunumber;
unsigned char *drawvect(Drawable* d, int x, int y, unsigned char *vect, int width, int high, Color &clr)
// x, y是左上角坐标,vect指向矢量图数据,width和high分别代表要绘制的图的宽和高(好像宽需要=高)
{
if (!vect) return nullptr;
int i = 0, j = 1; // 指向第一对数字
unsigned char ix, iy; // 从数组取出的第一对坐标放在这
double x1, y1;//, x2, y2; // 绘制直线的实数的坐标
if (high == 0) high = width; // 调节高宽
int ii1, jj1, ii2, jj2; // 最后算出的绘制直线的坐标
int isend = 0; //是否到达曲线尾
int isallend = 0; // 是否结束整个图像
do {
ix = vect[i];
iy = vect[j];
isend = ix & 1; // 奇数代表曲线尾
isallend = iy & 1; // 奇数代表整个图像结束
if (isallend) {
i += 2;
break; // 整个图像结束返回
}
if (isend) { // 如果这里曲线结束什么也不画进到下一条曲线
i += 2;
j += 2;
continue;
}
ix /= 2;
iy /= 2;
x1 = ix;
y1 = iy;
x1 = width * x1 / 128.0;
y1 = high * y1 / 128.0;
ii1 = (int)(x1 + x);
jj1 = (int)(y1 + y);
int k;
for (k = 0; k < 100000; k++) {
//x2 = x1;
//y2 = y1;
ii2 = ii1;
jj2 = jj1;
i += 2;
j += 2;
ix = vect[i];
iy = vect[j];
isend = ix & 1; //奇数代表曲线尾
isallend = iy & 1; //奇数代表整个图像结束
ix /= 2;
iy /= 2;
x1 = ix;
y1 = iy;
x1 = width * x1 / 128.0;
y1 = high * y1 / 128.0;
ii1 = (int)(x1 + x);
jj1 = (int)(y1 + y);
d->line(ii1, jj1, ii2, jj2, clr);
if (isend || isallend)
break;
}
i += 2; //指向下一条曲线
j += 2;
} while (!isallend);
return vect + i;
}
公开的:
字体绘制器(const int font_w, const int font_h)
: font_w(font_w), font_h(font_h) {}
/* 绘制文字 */
void drawvfont(Drawable* d, int x, int y, unsigned short z, Color &clr) {
int i, j, k, ii;
j = -1;
for (i = 0; i < zn; i++) {
if (chss[i] == z) {
j = i;
break;
}
}
if (j < 0) return;
k = 0;
for (i = 0; i < vennum; i++) {
if (k == j)
break;
for (ii =i; ii <vennum; ii++) {
if (vec[2*ii + 1] & 1) {
k++;
i = ii;
break;
}
}
}
if (k < j) return;
drawvect(d, x, y, vec + 2 * i, font_w, font_h, clr);
}
};
}
+141
View File
@@ -0,0 +1,141 @@
#pragma once
#include <iostream>
#include <vector>
#include <functional>
#include "../Dashun.h"
// 卷积神经网络构建辅助类(整合卷积、池化、展平层)
class CNNBuilder {
private:
std::vector<std::function<std::vector<float>(const std::vector<float>&, int, bool)>> forward_layers;
std::vector<std::function<std::vector<float>(const std::vector<float>&, int)>> backward_layers;
std::vector<std::tuple<std::string, int, int, int>> layer_shapes;
public:
// 添加卷积层
void add_conv_layer(ConvLayer& conv, int in_channels, int height, int width) {
conv.set_input_shape(height, width);
auto [out_height, out_width] = conv.get_output_shape();
forward_layers.push_back([&conv](const std::vector<float>& input, int batch, bool train) {
return conv.forward(input, batch, train);
});
backward_layers.push_back([&conv](const std::vector<float>& grad, int batch) {
auto a = conv.backward(grad, batch);
conv.update_parameters(0.3);
return a;
});
layer_shapes.push_back({"Conv", in_channels, out_height, out_width});
}
// 添加池化层
void add_pool_layer(PoolingLayer& pool, int in_channels, int height, int width) {
pool.set_input_shape(in_channels, height, width);
auto [out_channels, out_height, out_width] = pool.get_output_shape();
forward_layers.push_back([&pool](const std::vector<float>& input, int batch, bool train) {
return pool.forward(input, batch, train);
});
backward_layers.push_back([&pool](const std::vector<float>& grad, int batch) {
return pool.backward(grad, batch);
});
layer_shapes.push_back({"Pool", out_channels, out_height, out_width});
}
// 添加展平层
void add_flatten_layer(FlattenLayer& flatten, int in_channels, int height, int width) {
flatten.set_input_shape(in_channels, height, width);
int out_size = flatten.get_output_size();
forward_layers.push_back([&flatten](const std::vector<float>& input, int batch, bool train) {
return flatten.forward(input, batch, train);
});
backward_layers.push_back([&flatten](const std::vector<float>& grad, int batch) {
return flatten.backward(grad, batch);
});
layer_shapes.push_back({"Flatten", out_size, 1, 1});
}
// 前向传播(整个网络)
std::vector<float> forward(const std::vector<float>& input, int batch_size = 1, bool training = true) {
std::vector<float> current = input;
for (size_t i = 0; i < forward_layers.size(); i++) {
current = forward_layers[i](current, batch_size, training);
if (current.empty()) {
std::cerr << "Error in layer " << i << " ("
<< std::get<0>(layer_shapes[i]) << ")" << std::endl;
return std::vector<float>();
}
}
return current;
}
// 反向传播(整个网络)
std::vector<float> backward(const std::vector<float>& grad_output, int batch_size = 1) {
std::vector<float> current_grad = grad_output;
for (int i = backward_layers.size() - 1; i >= 0; i--) {
current_grad = backward_layers[i](current_grad, batch_size);
if (current_grad.empty()) {
std::cerr << "Error in backward pass of layer " << i << " ("
<< std::get<0>(layer_shapes[i]) << ")" << std::endl;
return std::vector<float>();
}
}
return current_grad;
}
// 打印网络结构
void print_architecture() const {
std::cout << "CNN Architecture:" << std::endl;
std::cout << "=================" << std::endl;
for (size_t i = 0; i < layer_shapes.size(); i++) {
auto [layer_type, channels, height, width] = layer_shapes[i];
if (layer_type == "Flatten") {
std::cout << "Layer " << i << ": " << layer_type
<< " -> Output size: " << channels << std::endl;
} else {
std::cout << "Layer " << i << ": " << layer_type
<< " -> Output shape: " << channels
<< "x" << height << "x" << width << std::endl;
}
}
}
};
/*
// 构建简单CNN
ConvLayer conv1(3, 16, 3, 1, 1);
PoolingLayer pool1(2, 2, 2);
ConvLayer conv2(16, 32, 3, 1, 1);
PoolingLayer pool2(2, 2, 2);
FlattenLayer flatten;
// 使用CNN构建器
CNNBuilder cnn;
cnn.add_conv_layer(conv1, 3, 32, 32);
cnn.add_pool_layer(pool1, 16, 32, 32);
cnn.add_conv_layer(conv2, 16, 16, 16);
cnn.add_pool_layer(pool2, 32, 16, 16);
cnn.add_flatten_layer(flatten, 32, 8, 8);
// 执行前向传播
auto output = cnn.forward(input_data, batch_size, true);
*/
+562
View File
@@ -0,0 +1,562 @@
#pragma once
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <chrono>
#include <random>
#include <numeric>
#include <functional>
class ConvLayer {
private:
// 参数
int input_channels;
int output_channels;
int kernel_size;
int stride;
int padding;
int input_height;
int input_width;
int output_height;
int output_width;
// Leaky ReLU参数
float negative_slope; // Leaky ReLU的负斜率,通常为0.01
// 权重和偏置
std::vector<float> weights; // shape: [output_channels, input_channels, kernel_size, kernel_size]
std::vector<float> bias; // shape: [output_channels]
// 梯度和动量
std::vector<float> d_weights; // 权重梯度
std::vector<float> d_bias; // 偏置梯度
std::vector<float> weight_momentum; // 动量
std::vector<float> bias_momentum; // 偏置动量
// 中间变量缓存(用于反向传播)
std::vector<float> col_buffer; // im2col后的矩阵
std::vector<float> input_cache; // 缓存的输入(用于计算输入梯度)
std::vector<float> output_cache; // 缓存的输出(激活前)
std::vector<float> activated_cache; // 激活后的输出缓存
std::vector<float> col_cache; // 缓存的col矩阵(用于权重梯度计算)
// 辅助函数
void im2col(const float* input, int batch_size);
void col2im(const float* col, float* im, int batch_size);
void sgemm(int M, int N, int K,
const float* A, const float* B, float* C,
bool transA = false, bool transB = false);
void sgemm_add(int M, int N, int K,
const float* A, const float* B, float* C,
float alpha = 1.0f, float beta = 1.0f,
bool transA = false, bool transB = false);
// Leaky ReLU激活函数及其导数
inline float leaky_relu(float x) const {
return x > 0 ? x : negative_slope * x;
}
inline float leaky_relu_derivative(float x) const {
return x > 0 ? 1.0f : negative_slope;
}
// 应用激活函数到整个张量
void apply_leaky_relu(float* data, int size) {
for (int i = 0; i < size; i++) {
data[i] = leaky_relu(data[i]);
}
}
// 计算激活函数的梯度
void compute_activation_gradient(const float* pre_activation, float* gradient, int size) {
for (int i = 0; i < size; i++) {
gradient[i] *= leaky_relu_derivative(pre_activation[i]);
}
}
public:
// 构造函数 - 添加negative_slope参数
ConvLayer(int in_channels, int out_channels,
int kernel_size, int stride = 1, int padding = 0,
float negative_slope = 0.01f)
: input_channels(in_channels),
output_channels(out_channels),
kernel_size(kernel_size),
stride(stride),
padding(padding),
negative_slope(negative_slope) {
// 初始化权重和偏置
int weight_size = out_channels * in_channels * kernel_size * kernel_size;
weights.resize(weight_size);
d_weights.resize(weight_size, 0.0f);
weight_momentum.resize(weight_size, 0.0f);
bias.resize(out_channels);
d_bias.resize(out_channels, 0.0f);
bias_momentum.resize(out_channels, 0.0f);
// He初始化(适合ReLU激活函数)
float std_dev = sqrt(2.0f / (in_channels * kernel_size * kernel_size));
std::default_random_engine generator;
std::normal_distribution<float> distribution(0.0f, std_dev);
for (int i = 0; i < weight_size; i++) {
weights[i] = distribution(generator);
}
for (int i = 0; i < out_channels; i++) {
bias[i] = 0.1f; // 小的正偏置
}
}
// 设置输入尺寸(计算输出尺寸)
void set_input_shape(int height, int width) {
input_height = height;
input_width = width;
output_height = (height + 2 * padding - kernel_size) / stride + 1;
output_width = (width + 2 * padding - kernel_size) / stride + 1;
// 预分配缓冲区
int col_height = input_channels * kernel_size * kernel_size;
int col_width = output_height * output_width;
col_buffer.resize(col_height * col_width);
// 清空缓存
input_cache.clear();
output_cache.clear();
activated_cache.clear();
col_cache.clear();
}
// 前向传播
std::vector<float> forward(const std::vector<float>& input, int batch_size = 1, bool training = true) {
// 计算输出大小
int output_size = batch_size * output_channels * output_height * output_width;
std::vector<float> output(output_size, 0.0f);
std::vector<float> pre_activation(output_size, 0.0f); // 激活前的输出
// 如果训练模式,缓存输入
if (training) {
input_cache = input;
}
// 对batch中的每个样本进行处理
for (int b = 0; b < batch_size; b++) {
const float* input_ptr = input.data() + b * input_channels * input_height * input_width;
// 1. im2col转换
im2col(input_ptr, 1);
// 2. 矩阵乘法:weights * col_buffer
int M = output_channels; // 输出通道数
int N = output_height * output_width; // 输出特征图大小
int K = input_channels * kernel_size * kernel_size; // 卷积核展平大小
// 如果训练模式,缓存col矩阵
if (training && b == 0) {
col_cache = col_buffer;
}
// 临时存储矩阵乘法结果(激活前)
std::vector<float> gemm_result(M * N, 0.0f);
sgemm(M, N, K, weights.data(), col_buffer.data(), gemm_result.data());
// 3. 加上偏置并存储到pre_activation
float* pre_activation_ptr = pre_activation.data() + b * output_channels * output_height * output_width;
float* output_ptr = output.data() + b * output_channels * output_height * output_width;
for (int oc = 0; oc < output_channels; oc++) {
float bias_val = bias[oc];
for (int i = 0; i < output_height * output_width; i++) {
float value = gemm_result[oc * N + i] + bias_val;
pre_activation_ptr[oc * output_height * output_width + i] = value;
// 4. 应用Leaky ReLU激活函数
output_ptr[oc * output_height * output_width + i] = leaky_relu(value);
}
}
}
// 如果训练模式,缓存激活前和激活后的输出
if (training) {
output_cache = pre_activation; // 缓存激活前的输出
activated_cache = output; // 缓存激活后的输出
}
return output; // 返回激活后的输出
}
// 反向传播
std::vector<float> backward(const std::vector<float>& grad_output, int batch_size = 1) {
// grad_output是激活函数后的梯度,需要先计算激活函数前的梯度
std::vector<float> grad_pre_activation(grad_output);
// 计算激活函数的梯度:grad_pre_activation = grad_output * leaky_relu_derivative(pre_activation)
for (int b = 0; b < batch_size; b++) {
const float* pre_activation_ptr = output_cache.data() + b * output_channels * output_height * output_width;
float* grad_ptr = grad_pre_activation.data() + b * output_channels * output_height * output_width;
for (int i = 0; i < output_channels * output_height * output_width; i++) {
grad_ptr[i] *= leaky_relu_derivative(pre_activation_ptr[i]);
}
}
// 1. 计算偏置梯度: d_bias = sum(grad_pre_activation, axis=(0,2,3))
std::fill(d_bias.begin(), d_bias.end(), 0.0f);
for (int b = 0; b < batch_size; b++) {
const float* grad_ptr = grad_pre_activation.data() + b * output_channels * output_height * output_width;
for (int oc = 0; oc < output_channels; oc++) {
float sum = 0.0f;
for (int i = 0; i < output_height * output_width; i++) {
sum += grad_ptr[oc * output_height * output_width + i];
}
d_bias[oc] += sum;
}
}
// 2. 计算权重梯度: d_weights = grad_pre_activation_col * col_cache^T
std::fill(d_weights.begin(), d_weights.end(), 0.0f);
int M = output_channels; // 输出通道数
int N = input_channels * kernel_size * kernel_size; // 卷积核展平大小
int K = output_height * output_width; // 输出特征图大小
// 将grad_pre_activation reshape为矩阵 [output_channels, batch_size * output_height * output_width]
std::vector<float> grad_pre_activation_mat(M * K * batch_size, 0.0f);
for (int b = 0; b < batch_size; b++) {
const float* grad_ptr = grad_pre_activation.data() + b * output_channels * output_height * output_width;
for (int oc = 0; oc < output_channels; oc++) {
for (int i = 0; i < output_height * output_width; i++) {
grad_pre_activation_mat[oc * (K * batch_size) + b * K + i] =
grad_ptr[oc * output_height * output_width + i];
}
}
}
// 扩展col_cache以匹配batch大小
std::vector<float> col_cache_batched(N * K * batch_size, 0.0f);
for (int b = 0; b < batch_size; b++) {
std::copy(col_cache.begin(), col_cache.end(),
col_cache_batched.begin() + b * N * K);
}
// 矩阵乘法: d_weights = grad_pre_activation_mat * col_cache_batched^T
// grad_pre_activation_mat: [M, K*batch_size], col_cache_batched^T: [K*batch_size, N]
sgemm_add(M, N, K * batch_size,
grad_pre_activation_mat.data(), col_cache_batched.data(), d_weights.data(),
1.0f, 0.0f, false, true);
// 3. 计算输入梯度: d_input_col = weights^T * grad_pre_activation_mat
// 然后通过col2im将d_input_col转换为d_input
std::vector<float> d_input(batch_size * input_channels * input_height * input_width, 0.0f);
// 计算d_input_col
std::vector<float> d_input_col(N * K * batch_size, 0.0f);
sgemm(N, K * batch_size, M,
weights.data(), grad_pre_activation_mat.data(), d_input_col.data(),
true, false);
// 将d_input_col通过col2im转换回输入形状
for (int b = 0; b < batch_size; b++) {
float* d_input_ptr = d_input.data() + b * input_channels * input_height * input_width;
col2im(d_input_col.data() + b * N * K, d_input_ptr, 1);
}
return d_input;
}
// 更新参数(使用SGD with momentum)
void update_parameters(float learning_rate = 0.01f, float momentum = 0.9f) {
// 更新权重
for (size_t i = 0; i < weights.size(); i++) {
weight_momentum[i] = momentum * weight_momentum[i] - learning_rate * d_weights[i];
weights[i] += weight_momentum[i];
}
// 更新偏置
for (size_t i = 0; i < bias.size(); i++) {
bias_momentum[i] = momentum * bias_momentum[i] - learning_rate * d_bias[i];
bias[i] += bias_momentum[i];
}
// 重置梯度(可选)
// std::fill(d_weights.begin(), d_weights.end(), 0.0f);
// std::fill(d_bias.begin(), d_bias.end(), 0.0f);
}
// Adam优化器更新参数
void update_parameters_adam(float learning_rate = 0.001f,
float beta1 = 0.9f, float beta2 = 0.999f,
float epsilon = 1e-8f, int t = 1) {
static std::vector<float> m_weights(weights.size(), 0.0f);
static std::vector<float> v_weights(weights.size(), 0.0f);
static std::vector<float> m_bias(bias.size(), 0.0f);
static std::vector<float> v_bias(bias.size(), 0.0f);
// 更新权重的一阶矩和二阶矩估计
for (size_t i = 0; i < weights.size(); i++) {
m_weights[i] = beta1 * m_weights[i] + (1 - beta1) * d_weights[i];
v_weights[i] = beta2 * v_weights[i] + (1 - beta2) * d_weights[i] * d_weights[i];
// 偏差校正
float m_hat = m_weights[i] / (1 - powf(beta1, t));
float v_hat = v_weights[i] / (1 - powf(beta2, t));
// 更新权重
weights[i] -= learning_rate * m_hat / (sqrtf(v_hat) + epsilon);
}
// 更新偏置的一阶矩和二阶矩估计
for (size_t i = 0; i < bias.size(); i++) {
m_bias[i] = beta1 * m_bias[i] + (1 - beta1) * d_bias[i];
v_bias[i] = beta2 * v_bias[i] + (1 - beta2) * d_bias[i] * d_bias[i];
// 偏差校正
float m_hat = m_bias[i] / (1 - powf(beta1, t));
float v_hat = v_bias[i] / (1 - powf(beta2, t));
// 更新偏置
bias[i] -= learning_rate * m_hat / (sqrtf(v_hat) + epsilon);
}
}
// 获取参数
const std::vector<float>& get_weights() const { return weights; }
const std::vector<float>& get_bias() const { return bias; }
const std::vector<float>& get_weight_gradients() const { return d_weights; }
const std::vector<float>& get_bias_gradients() const { return d_bias; }
// 设置参数(用于加载预训练模型)
void set_weights(const std::vector<float>& new_weights) { weights = new_weights; }
void set_bias(const std::vector<float>& new_bias) { bias = new_bias; }
// 设置Leaky ReLU参数
void set_leaky_relu_slope(float slope) { negative_slope = slope; }
// 获取输出尺寸
std::tuple<int, int> get_output_shape() const {
return std::make_tuple(output_height, output_width);
}
// 梯度检查(用于调试)
float gradient_check(const std::vector<float>& input,
const std::vector<float>& grad_output,
int weight_idx, float epsilon = 1e-5f) {
// 保存原始权重
float original_weight = weights[weight_idx];
// 计算f(x + epsilon)
weights[weight_idx] = original_weight + epsilon;
auto output_plus = forward(input, 1, false);
// 计算f(x - epsilon)
weights[weight_idx] = original_weight - epsilon;
auto output_minus = forward(input, 1, false);
// 恢复原始权重
weights[weight_idx] = original_weight;
// 计算数值梯度
float numerical_grad = 0.0f;
for (size_t i = 0; i < output_plus.size(); i++) {
numerical_grad += grad_output[i] * (output_plus[i] - output_minus[i]) / (2 * epsilon);
}
// 返回梯度差异
return fabs(numerical_grad - d_weights[weight_idx]);
}
};
// im2col实现 - 将图像转换为矩阵
void ConvLayer::im2col(const float* input, int batch_size) {
int col_height = input_channels * kernel_size * kernel_size;
int col_width = output_height * output_width;
int input_h_padded = input_height + 2 * padding;
int input_w_padded = input_width + 2 * padding;
// 创建填充后的输入(如果需要)
std::vector<float> padded_input;
const float* input_data = input;
if (padding > 0) {
padded_input.resize(input_channels * input_h_padded * input_w_padded, 0.0f);
for (int c = 0; c < input_channels; c++) {
for (int h = 0; h < input_height; h++) {
for (int w = 0; w < input_width; w++) {
int padded_idx = c * input_h_padded * input_w_padded +
(h + padding) * input_h_padded + (w + padding);
int orig_idx = c * input_height * input_width + h * input_width + w;
padded_input[padded_idx] = input[orig_idx];
}
}
}
input_data = padded_input.data();
}
// 执行im2col
int col_idx = 0;
for (int c = 0; c < input_channels; c++) {
for (int kh = 0; kh < kernel_size; kh++) {
for (int kw = 0; kw < kernel_size; kw++) {
for (int h = 0; h < output_height; h++) {
for (int w = 0; w < output_width; w++) {
int input_h = h * stride + kh;
int input_w = w * stride + kw;
int input_idx = c * input_h_padded * input_w_padded +
input_h * input_w_padded + input_w;
col_buffer[col_idx++] = input_data[input_idx];
}
}
}
}
}
}
// col2im实现 - 将矩阵转换回图像(用于梯度传播)
void ConvLayer::col2im(const float* col, float* im, int batch_size) {
int col_height = input_channels * kernel_size * kernel_size;
int col_width = output_height * output_width;
int input_h_padded = input_height + 2 * padding;
int input_w_padded = input_width + 2 * padding;
// 创建填充后的图像缓冲区
std::vector<float> padded_im(input_channels * input_h_padded * input_w_padded, 0.0f);
// 执行col2im
int col_idx = 0;
for (int c = 0; c < input_channels; c++) {
for (int kh = 0; kh < kernel_size; kh++) {
for (int kw = 0; kw < kernel_size; kw++) {
for (int h = 0; h < output_height; h++) {
for (int w = 0; w < output_width; w++) {
int input_h = h * stride + kh;
int input_w = w * stride + kw;
int input_idx = c * input_h_padded * input_w_padded +
input_h * input_w_padded + input_w;
padded_im[input_idx] += col[col_idx++];
}
}
}
}
}
// 去除padding,得到原始输入梯度
if (padding > 0) {
for (int c = 0; c < input_channels; c++) {
for (int h = 0; h < input_height; h++) {
for (int w = 0; w < input_width; w++) {
int padded_idx = c * input_h_padded * input_w_padded +
(h + padding) * input_w_padded + (w + padding);
int orig_idx = c * input_height * input_width + h * input_width + w;
im[orig_idx] = padded_im[padded_idx];
}
}
}
} else {
std::copy(padded_im.begin(), padded_im.end(), im);
}
}
// 优化的矩阵乘法
void ConvLayer::sgemm(int M, int N, int K,
const float* A, const float* B, float* C,
bool transA, bool transB) {
// 初始化输出为0
std::memset(C, 0, M * N * sizeof(float));
// 调用带参数的版本
sgemm_add(M, N, K, A, B, C, 1.0f, 0.0f, transA, transB);
}
// 带参数的矩阵乘法(支持转置)
void ConvLayer::sgemm_add(int M, int N, int K,
const float* A, const float* B, float* C,
float alpha, float beta,
bool transA, bool transB) {
// 如果beta为0,清空C
if (beta == 0.0f) {
std::memset(C, 0, M * N * sizeof(float));
}
// 优化:使用循环展开和缓存友好的访问模式
const int BLOCK_SIZE = 4;
// 处理A转置的情况
if (transA && !transB) {
// C = A^T * B
for (int i = 0; i < M; i++) {
for (int k = 0; k < K; k++) {
float a = A[k * M + i] * alpha;
if (a == 0.0f) continue;
const float* B_ptr = B + k * N;
float* C_ptr = C + i * N;
int j = 0;
for (; j <= N - BLOCK_SIZE; j += BLOCK_SIZE) {
C_ptr[j] += a * B_ptr[j];
C_ptr[j + 1] += a * B_ptr[j + 1];
C_ptr[j + 2] += a * B_ptr[j + 2];
C_ptr[j + 3] += a * B_ptr[j + 3];
}
for (; j < N; j++) {
C_ptr[j] += a * B_ptr[j];
}
}
}
} else if (!transA && transB) {
// C = A * B^T
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
float sum = 0.0f;
for (int k = 0; k < K; k++) {
sum += A[i * K + k] * B[j * K + k];
}
C[i * N + j] = beta * C[i * N + j] + alpha * sum;
}
}
} else if (transA && transB) {
// C = A^T * B^T = (B * A)^T
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
float sum = 0.0f;
for (int k = 0; k < K; k++) {
sum += A[k * M + i] * B[j * K + k];
}
C[i * N + j] = beta * C[i * N + j] + alpha * sum;
}
}
} else {
// C = A * B
for (int i = 0; i < M; i++) {
for (int k = 0; k < K; k++) {
float a = A[i * K + k] * alpha;
if (a == 0.0f) continue;
const float* B_ptr = B + k * N;
float* C_ptr = C + i * N;
int j = 0;
for (; j <= N - BLOCK_SIZE; j += BLOCK_SIZE) {
C_ptr[j] += a * B_ptr[j];
C_ptr[j + 1] += a * B_ptr[j + 1];
C_ptr[j + 2] += a * B_ptr[j + 2];
C_ptr[j + 3] += a * B_ptr[j + 3];
}
for (; j < N; j++) {
C_ptr[j] += a * B_ptr[j];
}
}
}
}
}
+366
View File
@@ -0,0 +1,366 @@
#pragma once
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <tuple>
#include "ConvLayer.hpp"
class FlattenLayer {
private:
// 输入输出尺寸
int batch_size;
int input_channels;
int input_height;
int input_width;
int output_size;
// 缓存输入形状(用于反向传播)
std::vector<int> input_shape_cache;
std::vector<float> input_cache; // 缓存输入数据(可选)
// 辅助函数
//void flatten_forward(const float* input, float* output);
//void flatten_backward(const float* grad_output, float* grad_input);
public:
// 构造函数
FlattenLayer() : batch_size(0), input_channels(0),
input_height(0), input_width(0), output_size(0) {}
// 设置输入尺寸(自动计算输出尺寸)
void set_input_shape(int channels, int height, int width, int batch = 1) {
input_channels = channels;
input_height = height;
input_width = width;
batch_size = batch;
// 计算输出大小
output_size = channels * height * width;
// 缓存输入形状
input_shape_cache = {channels, height, width, batch};
}
// 设置输入尺寸(从卷积/池化层获取)
template<typename... Args>
void set_input_shape(std::tuple<Args...> shape_tuple) {
set_input_shape(std::get<0>(shape_tuple),
std::get<1>(shape_tuple),
std::get<2>(shape_tuple));
}
// 前向传播
std::vector<float> forward(const std::vector<float>& input, int batch_size = 1, bool training = true) {
// 如果之前没有设置输入尺寸,自动推断
if (input_channels == 0 && input_height == 0 && input_width == 0) {
// 这里假设输入是完整的batch数据,需要外部调用set_input_shape
// 或者根据输入大小推断(但不知道高度和宽度)
std::cerr << "Warning: Input shape not set for FlattenLayer. "
<< "Please call set_input_shape() first." << std::endl;
return input; // 安全返回,但可能不正确
}
// 检查输入大小是否匹配
int expected_input_size = batch_size * input_channels * input_height * input_width;
if (input.size() != static_cast<size_t>(expected_input_size)) {
std::cerr << "Error: Input size mismatch in FlattenLayer. "
<< "Expected: " << expected_input_size
<< ", Got: " << input.size() << std::endl;
return std::vector<float>();
}
// 如果训练模式,缓存输入形状
if (training) {
this->batch_size = batch_size;
input_shape_cache = {input_channels, input_height, input_width, batch_size};
input_cache = input; // 可选:缓存输入数据
}
// 计算输出大小
int output_total_size = batch_size * output_size;
std::vector<float> output(output_total_size, 0.0f);
// 执行展平操作
flatten_forward(input.data(), output.data());
return output;
}
// 反向传播
std::vector<float> backward(const std::vector<float>& grad_output, int batch_size = 1) {
// 检查梯度大小是否匹配
int expected_grad_size = batch_size * output_size;
if (grad_output.size() != static_cast<size_t>(expected_grad_size)) {
std::cerr << "Error: Gradient size mismatch in FlattenLayer. "
<< "Expected: " << expected_grad_size
<< ", Got: " << grad_output.size() << std::endl;
return std::vector<float>();
}
// 恢复输入形状
if (input_shape_cache.size() >= 4) {
input_channels = input_shape_cache[0];
input_height = input_shape_cache[1];
input_width = input_shape_cache[2];
this->batch_size = input_shape_cache[3];
}
// 计算输入梯度大小
int input_total_size = this->batch_size * input_channels * input_height * input_width;
std::vector<float> grad_input(input_total_size, 0.0f);
// 执行反向展平操作
flatten_backward(grad_output.data(), grad_input.data());
return grad_input;
}
// 获取输出尺寸
int get_output_size() const {
return output_size;
}
// 获取输出尺寸(返回元组)
std::tuple<int> get_output_shape() const {
return std::make_tuple(output_size);
}
// 获取输入尺寸
std::tuple<int, int, int> get_input_shape() const {
return std::make_tuple(input_channels, input_height, input_width);
}
// 获取完整输入尺寸(包含批次)
std::tuple<int, int, int, int> get_full_input_shape() const {
return std::make_tuple(input_channels, input_height, input_width, batch_size);
}
// 打印层信息
void print_info() const {
std::cout << "Flatten Layer Info:" << std::endl;
std::cout << " Input Shape: " << input_channels << "x"
<< input_height << "x" << input_width;
if (batch_size > 0) {
std::cout << " (batch: " << batch_size << ")";
}
std::cout << std::endl;
std::cout << " Output Size: " << output_size;
if (batch_size > 0) {
std::cout << " (total: " << batch_size * output_size << ")";
}
std::cout << std::endl;
}
// 重置层状态
void reset() {
input_shape_cache.clear();
input_cache.clear();
batch_size = 0;
input_channels = 0;
input_height = 0;
input_width = 0;
output_size = 0;
}
// 检查是否已初始化
bool is_initialized() const {
return input_channels > 0 && input_height > 0 && input_width > 0;
}
private:
// 展平前向传播实现
void flatten_forward(const float* input, float* output) {
// 简单地将多维数据复制到一维数组
int elements_per_sample = input_channels * input_height * input_width;
for (int b = 0; b < batch_size; b++) {
const float* input_ptr = input + b * elements_per_sample;
float* output_ptr = output + b * output_size;
// 直接内存复制
std::copy(input_ptr, input_ptr + elements_per_sample, output_ptr);
// 或者使用循环(等效)
/*
for (int i = 0; i < elements_per_sample; i++) {
output_ptr[i] = input_ptr[i];
}
*/
}
}
// 展平反向传播实现
void flatten_backward(const float* grad_output, float* grad_input) {
// 反向传播只是将梯度重新整形为输入形状
int elements_per_sample = input_channels * input_height * input_width;
for (int b = 0; b < batch_size; b++) {
const float* grad_output_ptr = grad_output + b * output_size;
float* grad_input_ptr = grad_input + b * elements_per_sample;
// 直接内存复制
std::copy(grad_output_ptr, grad_output_ptr + elements_per_sample, grad_input_ptr);
// 或者使用循环(等效)
/*
for (int i = 0; i < elements_per_sample; i++) {
grad_input_ptr[i] = grad_output_ptr[i];
}
*/
}
}
};
// 动态展平层(自动推断输入形状)
class DynamicFlattenLayer {
private:
int batch_size;
int input_channels;
int input_height;
int input_width;
int output_size;
public:
DynamicFlattenLayer() : batch_size(0), input_channels(0),
input_height(0), input_width(0), output_size(0) {}
// 前向传播(自动推断形状)
std::vector<float> forward(const std::vector<float>& input,
std::tuple<int, int, int> input_shape,
int batch_size = 1,
bool training = true) {
// 从元组获取输入形状
input_channels = std::get<0>(input_shape);
input_height = std::get<1>(input_shape);
input_width = std::get<2>(input_shape);
this->batch_size = batch_size;
// 验证输入大小
int expected_size = batch_size * input_channels * input_height * input_width;
if (input.size() != static_cast<size_t>(expected_size)) {
std::cerr << "Error: Input size mismatch in DynamicFlattenLayer. "
<< "Expected: " << expected_size
<< ", Got: " << input.size()
<< " (shape: " << input_channels << "x"
<< input_height << "x" << input_width
<< ", batch: " << batch_size << ")" << std::endl;
return std::vector<float>();
}
// 计算输出大小
output_size = input_channels * input_height * input_width;
int total_output_size = batch_size * output_size;
std::vector<float> output(total_output_size);
// 执行展平
const float* input_ptr = input.data();
float* output_ptr = output.data();
int elements_per_sample = output_size;
for (int b = 0; b < batch_size; b++) {
std::copy(input_ptr, input_ptr + elements_per_sample, output_ptr);
input_ptr += elements_per_sample;
output_ptr += elements_per_sample;
}
return output;
}
// 反向传播
std::vector<float> backward(const std::vector<float>& grad_output, int batch_size = 1) {
// 检查是否已初始化
if (output_size == 0) {
std::cerr << "Error: DynamicFlattenLayer not initialized. "
<< "Call forward() first." << std::endl;
return std::vector<float>();
}
// 验证梯度大小
int expected_grad_size = batch_size * output_size;
if (grad_output.size() != static_cast<size_t>(expected_grad_size)) {
std::cerr << "Error: Gradient size mismatch in DynamicFlattenLayer. "
<< "Expected: " << expected_grad_size
<< ", Got: " << grad_output.size() << std::endl;
return std::vector<float>();
}
// 恢复输入形状
int total_input_size = batch_size * input_channels * input_height * input_width;
std::vector<float> grad_input(total_input_size);
// 执行反向展平
const float* grad_output_ptr = grad_output.data();
float* grad_input_ptr = grad_input.data();
int elements_per_sample = output_size;
for (int b = 0; b < batch_size; b++) {
std::copy(grad_output_ptr, grad_output_ptr + elements_per_sample, grad_input_ptr);
grad_output_ptr += elements_per_sample;
grad_input_ptr += elements_per_sample;
}
return grad_input;
}
// 获取输出尺寸
int get_output_size() const { return output_size; }
// 获取输入形状
std::tuple<int, int, int> get_input_shape() const {
return std::make_tuple(input_channels, input_height, input_width);
}
};
// 使用示例函数
inline void flatten_layer_example() {
std::cout << "=== Flatten Layer Example ===" << std::endl;
// 创建展平层
FlattenLayer flatten;
// 设置输入形状(例如从池化层获取的输出)
// 假设池化层输出:64通道,7x7特征图
flatten.set_input_shape(64, 7, 7);
// 创建模拟输入数据(batch_size=2)
int batch_size = 2;
std::vector<float> input(2 * 64 * 7 * 7);
for (size_t i = 0; i < input.size(); i++) {
input[i] = static_cast<float>(i) / 100.0f;
}
// 前向传播
std::cout << "Forward pass..." << std::endl;
auto output = flatten.forward(input, batch_size, true);
std::cout << "Input size: " << input.size() << std::endl;
std::cout << "Output size: " << output.size() << std::endl;
std::cout << "Expected output size per sample: " << flatten.get_output_size() << std::endl;
// 打印部分结果
std::cout << "First 10 output values: ";
for (int i = 0; i < std::min(10, static_cast<int>(output.size())); i++) {
std::cout << output[i] << " ";
}
std::cout << std::endl;
// 创建模拟梯度(全为1)
std::vector<float> grad_output(output.size(), 1.0f);
// 反向传播
std::cout << "Backward pass..." << std::endl;
auto grad_input = flatten.backward(grad_output, batch_size);
std::cout << "Gradient input size: " << grad_input.size() << std::endl;
std::cout << "Expected gradient input size: " << 2 * 64 * 7 * 7 << std::endl;
// 验证形状正确性
auto [channels, height, width] = flatten.get_input_shape();
std::cout << "Recovered input shape: " << channels
<< "x" << height << "x" << width << std::endl;
// 打印层信息
flatten.print_info();
}
+66
View File
@@ -0,0 +1,66 @@
#pragma once
#include <cmath>
#include <iostream>
/* FIXME: 设置为每层可调节的 */
/* FIXME: 现在这个“接口”并未提供抽象函数 */
constexpr float LEAKY_RELU_ALPHA = 0.01;
using f_t = float(*)(float); /* FIXME: 在qing::Thread附近已经定义了f_t标识符 */
/* 神经网络层接口 */
class ILayer {
public:
/* 激活函数枚举类 */
enum class ActivationFunc {
None, ReLU, Leaky_ReLU, Sigmoid, Tanh
};
/* 获取类型名 */
static const char* get_f_type(ActivationFunc& af);
/* 通过类型名获取实体 */
static ActivationFunc parse_ff_type(const std::string& name);
/* 向输出流存储模型 */
virtual void save(std::ostream& out) = 0;
/* 从输入流加载模型
* FIXME: 这个方法我做成了工厂模式
*/
//virtual void load(std::istream& in) = 0;
protected:
/* 获取函数对象 - 获取激活函数 */
static f_t getf(ActivationFunc type);
/* 获取函数对象 - 求激活函数导数 */
static f_t getfdot(ActivationFunc type);
/* ReLU 激活函数 */
static float relu(float x);
/* ReLU 导数 */
static float relu_derivative(float x);
/*
* leaky ReLU 激活函数
*
* NOTE: alpha太小或者选择使用ReLU函数的情况下,会导致梯度消失
* NOTE: Leaky ReLU 不会使梯度完全消失,但也会使它变得很小
*/
static float leaky_relu(float x);
/* leaky ReLU 导数 */
static float leaky_relu_derivative(float x);
/* Sigmoid 激活函数 */
static float sigmoid(float x);
/* Sigmoid 导数 */
static float sigmoid_derivative(float x);
/* Tanh 激活函数 */
static float tanh(float x);
/* Tanh 导数 */
static float tanh_derivative(float x);
};
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#include "../Dashun.h"
class ILayerConvolutional: public ILayer, public ILearnable {};
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include "nn/ILayer.hpp"
#include "nn/ILearnable.hpp"
class ILayerFullyConnectted: public ILayer, public ILearnable {
public:
/* 正向传播,返回神经网络输出 */
virtual std::vector<float> forward(const std::vector<float>& inputs) = 0;
/* 反向传播,返回上一层的误差,方便堆叠 */
virtual std::vector<float> backward(const std::vector<float>& error) = 0;
};
+5
View File
@@ -0,0 +1,5 @@
#pragma once
class ILearnable {
public:
virtual void update(float discount) = 0;
};
+523
View File
@@ -0,0 +1,523 @@
#pragma once
#define cimg_use_png /* -lpng */
#define cimg_use_jpeg /* -ljpeg */
#define cimg_display 0 // 禁用显示窗口,用于服务器环境
#include "CImg.h"
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
#include <cmath>
#include <memory>
#include <sys/stat.h> // 用于检查文件是否存在
#include <dirent.h> // 用于目录遍历
class ImageLoader {
private:
// 图像参数
int target_channels; // 目标通道数(如RGB=3, 灰度=1)
int target_height; // 目标高度
int target_width; // 目标宽度
bool normalize_to_01; // 是否归一化到[0,1]
bool normalize_mean_std; // 是否使用均值标准差归一化
float mean_val; // 均值(归一化用)
float std_val; // 标准差(归一化用)
bool auto_mean_std; // 是否自动计算均值标准差
// 预计算的均值和标准差(针对每个通道)
std::vector<float> channel_means;
std::vector<float> channel_stds;
// 用于调试的verbose标志
bool verbose;
// 辅助函数:检查文件是否存在(POSIX兼容)
bool file_exists(const std::string& filename) {
struct stat buffer;
return (stat(filename.c_str(), &buffer) == 0);
}
// 辅助函数:检查是否为普通文件
bool is_regular_file(const std::string& path) {
struct stat path_stat;
if (stat(path.c_str(), &path_stat) != 0) {
return false;
}
return S_ISREG(path_stat.st_mode);
}
// 辅助函数:获取文件扩展名
std::string get_file_extension(const std::string& filename) {
size_t dot_pos = filename.find_last_of(".");
if (dot_pos == std::string::npos) {
return "";
}
return filename.substr(dot_pos);
}
// 辅助函数:转换为小写
std::string to_lower(const std::string& str) {
std::string result = str;
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
return result;
}
public:
// 构造函数
ImageLoader(int channels = 3, int height = 224, int width = 224,
bool norm_01 = true, bool norm_mean_std = false,
float mean = 0.0f, float std = 1.0f, bool auto_compute = false)
: target_channels(channels),
target_height(height),
target_width(width),
normalize_to_01(norm_01),
normalize_mean_std(norm_mean_std),
mean_val(mean),
std_val(std),
auto_mean_std(auto_compute),
verbose(false) {
// 初始化通道统计量
channel_means.resize(target_channels, 0.0f);
channel_stds.resize(target_channels, 1.0f);
}
// 从文件加载单张图片
std::vector<float> load_image(const std::string& filepath, bool verbose = false) {
this->verbose = verbose;
try {
// 检查文件是否存在
if (!file_exists(filepath)) {
throw std::runtime_error("File not found: " + filepath);
}
// 加载图像
cimg_library::CImg<unsigned char> img(filepath.c_str());
if (verbose) {
std::cout << "Loaded image: " << filepath << std::endl;
std::cout << "Original size: " << img.width() << "x" << img.height()
<< "x" << img.spectrum() << std::endl;
}
// 转换为目标格式并预处理
return preprocess_image(img, verbose);
} catch (const std::exception& e) {
std::cerr << "Error loading image " << filepath << ": " << e.what() << std::endl;
return {};
}
}
// 从CImg对象加载图片
std::vector<float> load_from_cimg(const cimg_library::CImg<unsigned char>& input_img, bool verbose = false) {
this->verbose = verbose;
try {
// 创建图像的副本(因为预处理函数可能会修改图像)
cimg_library::CImg<unsigned char> img = input_img;
if (verbose) {
std::cout << "Loading from CImg object" << std::endl;
std::cout << "Original size: " << img.width() << "x" << img.height()
<< "x" << img.spectrum() << std::endl;
}
// 检查图像是否为空
if (img.is_empty()) {
throw std::runtime_error("Input CImg object is empty");
}
// 转换为目标格式并预处理
return preprocess_image(img, verbose);
} catch (const std::exception& e) {
std::cerr << "Error loading image from CImg object: " << e.what() << std::endl;
return {};
}
}
// 从CImg对象加载图片(移动语义版本)
std::vector<float> load_from_cimg(cimg_library::CImg<unsigned char>&& input_img, bool verbose = false) {
this->verbose = verbose;
try {
// 直接使用移动过来的图像对象
cimg_library::CImg<unsigned char> img = std::move(input_img);
if (verbose) {
std::cout << "Loading from CImg object (move semantics)" << std::endl;
std::cout << "Original size: " << img.width() << "x" << img.height()
<< "x" << img.spectrum() << std::endl;
}
// 检查图像是否为空
if (img.is_empty()) {
throw std::runtime_error("Input CImg object is empty");
}
// 转换为目标格式并预处理
return preprocess_image(img, verbose);
} catch (const std::exception& e) {
std::cerr << "Error loading image from CImg object: " << e.what() << std::endl;
return {};
}
}
// 批量加载图片
std::vector<float> load_batch(const std::vector<std::string>& filepaths, bool verbose = false) {
this->verbose = verbose;
std::vector<float> batch_data;
int loaded_count = 0;
for (const auto& filepath : filepaths) {
auto img_data = load_image(filepath, verbose);
if (!img_data.empty()) {
if (batch_data.empty()) {
batch_data = img_data;
} else {
// 追加到batch数据中
batch_data.insert(batch_data.end(), img_data.begin(), img_data.end());
}
loaded_count++;
}
}
if (verbose) {
std::cout << "Successfully loaded " << loaded_count << " images out of "
<< filepaths.size() << std::endl;
}
return batch_data;
}
// 从文件夹加载所有图片
std::vector<float> load_from_directory(const std::string& dir_path,
const std::vector<std::string>& extensions = {".jpg", ".jpeg", ".png", ".bmp"},
bool verbose = false) {
this->verbose = verbose;
std::vector<std::string> image_files;
try {
// 打开目录
DIR* dir = opendir(dir_path.c_str());
if (!dir) {
throw std::runtime_error("Cannot open directory: " + dir_path);
}
// 读取目录项
struct dirent* entry;
while ((entry = readdir(dir)) != nullptr) {
std::string filename = entry->d_name;
// 跳过"."和".."
if (filename == "." || filename == "..") {
continue;
}
// 构建完整路径
std::string full_path = dir_path;
if (dir_path.back() != '/') {
full_path += "/";
}
full_path += filename;
// 检查是否为普通文件
if (is_regular_file(full_path)) {
// 检查文件扩展名
std::string ext = get_file_extension(filename);
std::string ext_lower = to_lower(ext);
// 检查是否在允许的扩展名列表中
bool valid_extension = false;
for (const auto& allowed_ext : extensions) {
if (ext_lower == to_lower(allowed_ext)) {
valid_extension = true;
break;
}
}
if (valid_extension) {
image_files.push_back(full_path);
}
}
}
closedir(dir);
if (verbose) {
std::cout << "Found " << image_files.size() << " images in directory: "
<< dir_path << std::endl;
}
return load_batch(image_files, verbose);
} catch (const std::exception& e) {
std::cerr << "Error reading directory " << dir_path << ": " << e.what() << std::endl;
return {};
}
}
// 计算数据集统计量(用于归一化)
void compute_dataset_statistics(const std::vector<std::string>& filepaths,
int max_images = 1000) {
std::cout << "Computing dataset statistics..." << std::endl;
int num_images = std::min(static_cast<int>(filepaths.size()), max_images);
std::vector<std::vector<float>> channel_sums(target_channels, std::vector<float>());
// 采样计算
for (int i = 0; i < num_images; i++) {
auto img_data = load_image(filepaths[i], false);
if (!img_data.empty()) {
// 计算每个通道的像素值
int pixels_per_channel = target_height * target_width;
for (int c = 0; c < target_channels; c++) {
const float* channel_start = img_data.data() + c * pixels_per_channel;
channel_sums[c].insert(channel_sums[c].end(),
channel_start,
channel_start + pixels_per_channel);
}
}
if (i % 100 == 0) {
std::cout << "Processed " << i << " images..." << std::endl;
}
}
// 计算均值和标准差
for (int c = 0; c < target_channels; c++) {
if (!channel_sums[c].empty()) {
// 计算均值
float sum = std::accumulate(channel_sums[c].begin(), channel_sums[c].end(), 0.0f);
channel_means[c] = sum / channel_sums[c].size();
// 计算标准差
float variance = 0.0f;
for (float val : channel_sums[c]) {
variance += (val - channel_means[c]) * (val - channel_means[c]);
}
variance /= channel_sums[c].size();
channel_stds[c] = std::sqrt(variance);
std::cout << "Channel " << c << ": mean = " << channel_means[c]
<< ", std = " << channel_stds[c] << std::endl;
}
}
std::cout << "Statistics computation completed." << std::endl;
}
// 获取批次数(根据总像素数计算)
int get_batch_size(const std::vector<float>& data) const {
int single_image_size = target_channels * target_height * target_width;
if (single_image_size == 0) return 0;
return data.size() / single_image_size;
}
// 设置归一化参数
void set_normalization_params(const std::vector<float>& means,
const std::vector<float>& stds) {
if (means.size() == target_channels && stds.size() == target_channels) {
channel_means = means;
channel_stds = stds;
} else {
std::cerr << "Error: Means and stds must have size " << target_channels << std::endl;
}
}
// 获取归一化参数
std::pair<std::vector<float>, std::vector<float>> get_normalization_params() const {
return {channel_means, channel_stds};
}
// 显示图像信息(用于调试)
void display_image_info(const std::vector<float>& image_data, int channels_to_show = 3) const {
int single_image_size = target_channels * target_height * target_width;
std::cout << "Image data info:" << std::endl;
std::cout << " Dimensions: " << target_channels << " x " << target_height
<< " x " << target_width << std::endl;
std::cout << " Total values: " << image_data.size() << std::endl;
if (!image_data.empty()) {
// 计算统计信息
float min_val = *std::min_element(image_data.begin(), image_data.end());
float max_val = *std::max_element(image_data.begin(), image_data.end());
float sum_val = std::accumulate(image_data.begin(), image_data.end(), 0.0f);
float mean_val = sum_val / image_data.size();
std::cout << " Min value: " << min_val << std::endl;
std::cout << " Max value: " << max_val << std::endl;
std::cout << " Mean value: " << mean_val << std::endl;
// 显示每个通道的前几个像素
int pixels_per_channel = target_height * target_width;
for (int c = 0; c < std::min(target_channels, channels_to_show); c++) {
std::cout << " Channel " << c << " (first 5 pixels): ";
for (int i = 0; i < std::min(5, pixels_per_channel); i++) {
std::cout << image_data[c * pixels_per_channel + i] << " ";
}
std::cout << std::endl;
}
}
}
// 获取目标尺寸(公开访问器)
int get_target_height() const { return target_height; }
int get_target_width() const { return target_width; }
int get_target_channels() const { return target_channels; }
// 设置verbose模式
void set_verbose(bool v) { verbose = v; }
private:
// 图像预处理主函数
std::vector<float> preprocess_image(cimg_library::CImg<unsigned char>& img, bool verbose = false) {
// 1. 调整通道数
adjust_channels(img);
// 2. 调整尺寸
resize_image(img);
// 3. 转换为浮点数
std::vector<float> float_data = convert_to_float(img);
// 4. 重新排列维度为 [channels, height, width]
std::vector<float> reordered_data = reorder_dimensions(float_data, img);
// 5. 归一化
if (normalize_to_01) {
normalize_0_1(reordered_data);
}
if (normalize_mean_std) {
normalize_mean_std_channels(reordered_data);
}
if (verbose) {
std::cout << "Processed image size: " << target_channels << "x"
<< target_height << "x" << target_width << std::endl;
}
return reordered_data;
}
// 调整通道数
void adjust_channels(cimg_library::CImg<unsigned char>& img) {
int current_channels = img.spectrum();
if (current_channels == 1 && target_channels == 3) {
// 灰度转RGB:复制单通道到三个通道
cimg_library::CImg<unsigned char> rgb_img(img.width(), img.height(), img.depth(), 3);
cimg_forXYZ(img, x, y, z) {
rgb_img(x, y, z, 0) = img(x, y, z, 0); // R
rgb_img(x, y, z, 1) = img(x, y, z, 0); // G
rgb_img(x, y, z, 2) = img(x, y, z, 0); // B
}
img = rgb_img;
} else if (current_channels == 3 && target_channels == 1) {
// RGB转灰度:使用标准公式
img = img.get_channel(0) * 0.299f +
img.get_channel(1) * 0.587f +
img.get_channel(2) * 0.114f;
} else if (current_channels == 4 && target_channels == 3) {
// RGBA转RGB:移除alpha通道
img.channels(0, 2);
} else if (current_channels > target_channels) {
// 保留前target_channels个通道
img.channels(0, target_channels - 1);
} else if (current_channels < target_channels) {
// 重复最后一个通道来补足
int channels_to_add = target_channels - current_channels;
for (int i = 0; i < channels_to_add; i++) {
img.append(img.get_channel(current_channels - 1), 'c');
}
}
}
// 调整图像尺寸
void resize_image(cimg_library::CImg<unsigned char>& img) {
if (img.width() != target_width || img.height() != target_height) {
if (verbose) {
std::cout << "Resizing from " << img.width() << "x" << img.height()
<< " to " << target_width << "x" << target_height << std::endl;
}
// 使用CImg的resize函数
img.resize(target_width, target_height, -100, -100, 3); // 3 = 三次样条插值
}
}
// 转换为浮点数
std::vector<float> convert_to_float(const cimg_library::CImg<unsigned char>& img) {
std::vector<float> float_data;
float_data.reserve(img.width() * img.height() * img.spectrum());
cimg_forC(img, c) {
cimg_forXY(img, x, y) {
float_data.push_back(static_cast<float>(img(x, y, 0, c)));
}
}
return float_data;
}
// 重新排列维度为 [channels, height, width]
std::vector<float> reorder_dimensions(const std::vector<float>& data,
const cimg_library::CImg<unsigned char>& img) {
int channels = img.spectrum();
int height = img.height();
int width = img.width();
std::vector<float> reordered(channels * height * width);
// CImg默认布局: [width, height, depth, spectrum]
// 我们需要: [channels, height, width]
for (int c = 0; c < channels; c++) {
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
// CImg索引: (x, y, z, c) = (w, h, 0, c)
int src_idx = c * (height * width) + h * width + w;
int dst_idx = c * (height * width) + h * width + w;
reordered[dst_idx] = data[src_idx];
}
}
}
return reordered;
}
// 归一化到[0,1]
void normalize_0_1(std::vector<float>& data) {
for (auto& val : data) {
val /= 255.0f;
}
}
// 使用均值标准差归一化(逐通道)
void normalize_mean_std_channels(std::vector<float>& data) {
int pixels_per_channel = target_height * target_width;
for (int c = 0; c < target_channels; c++) {
float mean = auto_mean_std ? channel_means[c] : mean_val;
float std = auto_mean_std ? channel_stds[c] : std_val;
for (int i = 0; i < pixels_per_channel; i++) {
int idx = c * pixels_per_channel + i;
data[idx] = (data[idx] - mean) / std;
}
}
}
};
+64
View File
@@ -0,0 +1,64 @@
/*
___col___
/ \
+---------+ \
| + + + + | | row - 第一维度
| + + + + | |
+---------+ /
*/
#pragma once
#include <cmath>
#include <vector>
#include <iostream>
#include <stdexcept>
#include "cn/中文化.hpp"
命名空间 qing {
模板 <类型名 T>
/* 矩阵类 - 二维向量 (FIXME: 1.增加函数广播方法。2.增加+=操作符等)。3. 增加初始化方法*/
类 Matrx {
公开的:
Matrx();
Matrx(常量 长型 row, 常量 长型 col); /* 该构造函数用于从头开始构造 */
Matrx(常量 长型 row, 常量 长型 col, 常量 std::vector<T>& src); /* 该构造函数用于通过已有的vector进行构造 */
const T& get(long row, long col) const; /* 访问器 - 访问矩阵元素 */
T& witch(long row, long col); /* 访问器 - 访问矩阵元素 */
const std::vector<T>& get_data() const; /* 访问器 - 用于获取原始数据 */
const long get_col() const; /* 获取列数 */
const long get_row() const; /* 获取行数 */
bool has_same_shape(const Matrx<T>& other); /* 两个矩阵是否具有相同形状 */
bool has_dot_shape(const Matrx<T>& other); /* 具有矩阵乘法的形状(标准:A的列数与B的行数相等) */
void assert_same_shape(const Matrx<T>& other); /* 断言两个矩阵具有相同形状 */
void assert_dot_shape(const Matrx<T>& other); /* 断言两个矩阵具有点积前置形状 */
Matrx<T> operator+(const Matrx<T>& other); /* 加法操作符重定义 - 矩阵加法 */
Matrx<T> operator+(T& item); /* 矩阵加法广播 */
Matrx<T> operator-(const Matrx<T>& other); /* 操作符重载 - 矩阵减法 */
Matrx<T> operator-(T& item); /* 操作符重载 - 矩阵减法广播 */
const Matrx<T> operator*(const Matrx<T>& other); /* 操作符重载 - 矩阵乘法 */
Matrx<T> operator*(T& item); /* 操作符重载 - 矩阵乘法广播 */
Matrx<T> operator/(T& item); /* 操作符重载 - 矩阵除法广播 */
/* 储存矩阵 */
void save(std::ostream& out) {
out << row << " " << col; /* 储存形状 */
for (const auto& item: arr) { /* 储存数据 */
out << " ";
out << item;
}
}
/* FIXME: 不写load()方法的原因是使用工厂模式较为方便 */
/* FIXME: 增加矩阵转置操作 */
/* FIXME: 添加广播以使得矩阵与向量相乘 */
/* FIXME: 增加元素对应乘积 */
private:
long row, col;
std::vector<T> arr; /* 存储矩阵的张量 */
};
}
+199
View File
@@ -0,0 +1,199 @@
/*
* MatrixCuda.cuh - CUDA加速的矩阵运算库
*
* 基于现有的Matrx类设计,提供GPU加速的矩阵运算
* 支持float和double类型的矩阵运算
*
* 注意:使用CUDA需要安装NVIDIA CUDA Toolkit
*/
#pragma once
#ifdef __CUDACC__
#define CUDA_HOST_DEVICE __host__ __device__
#define CUDA_HOST __host__
#define CUDA_DEVICE __device__
#else
#define CUDA_HOST_DEVICE
#define CUDA_HOST
#define CUDA_DEVICE
#endif
#include <cuda_runtime.h>
#include <device_launch_parameters.h>
#include <iostream>
#include <vector>
#include <stdexcept>
#include <cstring>
namespace qing {
// 前向声明
template <typename T>
class MatrixCuda;
// CUDA内核函数声明
namespace cuda_kernels {
// 矩阵加法内核
template <typename T>
__global__ void matrix_add_kernel(const T* A, const T* B, T* C,
long rows, long cols, long total_elements);
// 矩阵减法内核
template <typename T>
__global__ void matrix_sub_kernel(const T* A, const T* B, T* C,
long rows, long cols, long total_elements);
// 矩阵标量乘法内核
template <typename T>
__global__ void matrix_scalar_mul_kernel(const T* A, T scalar, T* C,
long total_elements);
// 矩阵标量加法内核
template <typename T>
__global__ void matrix_scalar_add_kernel(const T* A, T scalar, T* C,
long total_elements);
// 矩阵转置内核
template <typename T>
__global__ void matrix_transpose_kernel(const T* A, T* C,
long rows, long cols);
// 矩阵元素对应乘积内核
template <typename T>
__global__ void matrix_hadamard_kernel(const T* A, const T* B, T* C,
long total_elements);
// 矩阵复制内核
template <typename T>
__global__ void matrix_copy_kernel(const T* src, T* dst, long total_elements);
} // namespace cuda_kernels
/**
* @brief CUDA加速的矩阵类
*
* 这个类提供了GPU加速的矩阵运算,包括:
* - 矩阵加法、减法
* - 标量运算
* - 矩阵转置
* - 元素对应乘积
* - 矩阵乘法(使用cuBLAS)
*/
template <typename T>
class MatrixCuda {
public:
// 构造函数和析构函数
CUDA_HOST MatrixCuda();
CUDA_HOST MatrixCuda(long rows, long cols);
CUDA_HOST MatrixCuda(long rows, long cols, const std::vector<T>& data);
CUDA_HOST MatrixCuda(const MatrixCuda<T>& other);
CUDA_HOST MatrixCuda(MatrixCuda<T>&& other) noexcept;
CUDA_HOST ~MatrixCuda();
// 赋值运算符
CUDA_HOST MatrixCuda<T>& operator=(const MatrixCuda<T>& other);
CUDA_HOST MatrixCuda<T>& operator=(MatrixCuda<T>&& other) noexcept;
// 内存管理
CUDA_HOST void allocate_device_memory();
CUDA_HOST void free_device_memory();
CUDA_HOST void copy_to_device();
CUDA_HOST void copy_from_device();
CUDA_HOST void sync_device_to_host();
CUDA_HOST void sync_host_to_device();
// 访问器
CUDA_HOST_DEVICE long get_rows() const { return rows_; }
CUDA_HOST_DEVICE long get_cols() const { return cols_; }
CUDA_HOST_DEVICE long get_total_elements() const { return rows_ * cols_; }
CUDA_HOST const std::vector<T>& get_host_data() const { return host_data_; }
CUDA_HOST T* get_device_data() const { return device_data_; }
CUDA_HOST T get_element(long row, long col) const;
CUDA_HOST void set_element(long row, long col, T value);
// 形状检查
CUDA_HOST bool has_same_shape(const MatrixCuda<T>& other) const;
CUDA_HOST bool has_dot_shape(const MatrixCuda<T>& other) const;
CUDA_HOST void assert_same_shape(const MatrixCuda<T>& other) const;
CUDA_HOST void assert_dot_shape(const MatrixCuda<T>& other) const;
// 矩阵运算(GPU加速)
CUDA_HOST MatrixCuda<T> add(const MatrixCuda<T>& other) const;
CUDA_HOST MatrixCuda<T> subtract(const MatrixCuda<T>& other) const;
CUDA_HOST MatrixCuda<T> multiply_scalar(T scalar) const;
CUDA_HOST MatrixCuda<T> add_scalar(T scalar) const;
CUDA_HOST MatrixCuda<T> transpose() const;
CUDA_HOST MatrixCuda<T> hadamard_product(const MatrixCuda<T>& other) const;
// 运算符重载
CUDA_HOST MatrixCuda<T> operator+(const MatrixCuda<T>& other) const;
CUDA_HOST MatrixCuda<T> operator+(T scalar) const;
CUDA_HOST MatrixCuda<T> operator-(const MatrixCuda<T>& other) const;
CUDA_HOST MatrixCuda<T> operator-(T scalar) const;
CUDA_HOST MatrixCuda<T> operator*(T scalar) const;
CUDA_HOST MatrixCuda<T> operator/(T scalar) const;
// 复合赋值运算符
CUDA_HOST MatrixCuda<T>& operator+=(const MatrixCuda<T>& other);
CUDA_HOST MatrixCuda<T>& operator+=(T scalar);
CUDA_HOST MatrixCuda<T>& operator-=(const MatrixCuda<T>& other);
CUDA_HOST MatrixCuda<T>& operator-=(T scalar);
CUDA_HOST MatrixCuda<T>& operator*=(T scalar);
CUDA_HOST MatrixCuda<T>& operator/=(T scalar);
// 矩阵乘法(使用cuBLAS)
CUDA_HOST MatrixCuda<T> matmul(const MatrixCuda<T>& other) const;
CUDA_HOST MatrixCuda<T> operator*(const MatrixCuda<T>& other) const;
// 工具函数
CUDA_HOST void print(const std::string& name = "") const;
CUDA_HOST void fill(T value);
CUDA_HOST void fill_random(T min = 0, T max = 1);
CUDA_HOST bool is_valid() const;
// 静态工厂方法
CUDA_HOST static MatrixCuda<T> zeros(long rows, long cols);
CUDA_HOST static MatrixCuda<T> ones(long rows, long cols);
CUDA_HOST static MatrixCuda<T> identity(long size);
CUDA_HOST static MatrixCuda<T> random(long rows, long cols, T min = 0, T max = 1);
// 从现有Matrx类转换
CUDA_HOST static MatrixCuda<T> from_matrx(const class Matrx<T>& mat);
CUDA_HOST class Matrx<T> to_matrx() const;
private:
long rows_;
long cols_;
std::vector<T> host_data_; // 主机内存数据
T* device_data_; // 设备内存数据
bool device_allocated_; // 设备内存是否已分配
bool host_modified_; // 主机数据是否已修改
bool device_modified_; // 设备数据是否已修改
// 私有辅助函数
CUDA_HOST void check_index(long row, long col) const;
CUDA_HOST void initialize_from_vector(const std::vector<T>& data);
// CUDA错误检查
CUDA_HOST static void check_cuda_error(cudaError_t error, const char* file, int line);
// 计算线程块和网格大小
CUDA_HOST static dim3 get_block_size(long total_elements);
CUDA_HOST static dim3 get_grid_size(long total_elements, dim3 block_size);
};
// CUDA错误检查宏
#define CHECK_CUDA_ERROR(err) (MatrixCuda<float>::check_cuda_error(err, __FILE__, __LINE__))
// 显式实例化声明
extern template class MatrixCuda<float>;
extern template class MatrixCuda<double>;
} // namespace qing
// 包含内联实现
#include "MatrixCuda.inl.cuh"
+739
View File
@@ -0,0 +1,739 @@
/*
* MatrixCuda.inl.cuh - MatrixCuda类的内联实现
* 包含CUDA内核函数和模板成员函数的实现
*/
#ifndef MATRIX_CUDA_INL_CUH
#define MATRIX_CUDA_INL_CUH
#include "MatrixCuda.cuh"
#include <random>
#include <chrono>
#include <algorithm>
namespace qing {
// ============================================================================
// CUDA内核函数实现
// ============================================================================
namespace cuda_kernels {
// 矩阵加法内核
template <typename T>
__global__ void matrix_add_kernel(const T* A, const T* B, T* C,
long rows, long cols, long total_elements) {
long idx = blockIdx.x * blockDim.x + threadIdx.x;
long stride = blockDim.x * gridDim.x;
for (long i = idx; i < total_elements; i += stride) {
C[i] = A[i] + B[i];
}
}
// 矩阵减法内核
template <typename T>
__global__ void matrix_sub_kernel(const T* A, const T* B, T* C,
long rows, long cols, long total_elements) {
long idx = blockIdx.x * blockDim.x + threadIdx.x;
long stride = blockDim.x * gridDim.x;
for (long i = idx; i < total_elements; i += stride) {
C[i] = A[i] - B[i];
}
}
// 矩阵标量乘法内核
template <typename T>
__global__ void matrix_scalar_mul_kernel(const T* A, T scalar, T* C,
long total_elements) {
long idx = blockIdx.x * blockDim.x + threadIdx.x;
long stride = blockDim.x * gridDim.x;
for (long i = idx; i < total_elements; i += stride) {
C[i] = A[i] * scalar;
}
}
// 矩阵标量加法内核
template <typename T>
__global__ void matrix_scalar_add_kernel(const T* A, T scalar, T* C,
long total_elements) {
long idx = blockIdx.x * blockDim.x + threadIdx.x;
long stride = blockDim.x * gridDim.x;
for (long i = idx; i < total_elements; i += stride) {
C[i] = A[i] + scalar;
}
}
// 矩阵转置内核
template <typename T>
__global__ void matrix_transpose_kernel(const T* A, T* C,
long rows, long cols) {
long row = blockIdx.y * blockDim.y + threadIdx.y;
long col = blockIdx.x * blockDim.x + threadIdx.x;
if (row < rows && col < cols) {
// A[row, col] -> C[col, row]
C[col * rows + row] = A[row * cols + col];
}
}
// 矩阵元素对应乘积内核
template <typename T>
__global__ void matrix_hadamard_kernel(const T* A, const T* B, T* C,
long total_elements) {
long idx = blockIdx.x * blockDim.x + threadIdx.x;
long stride = blockDim.x * gridDim.x;
for (long i = idx; i < total_elements; i += stride) {
C[i] = A[i] * B[i];
}
}
// 矩阵复制内核
template <typename T>
__global__ void matrix_copy_kernel(const T* src, T* dst, long total_elements) {
long idx = blockIdx.x * blockDim.x + threadIdx.x;
long stride = blockDim.x * gridDim.x;
for (long i = idx; i < total_elements; i += stride) {
dst[i] = src[i];
}
}
} // namespace cuda_kernels
// ============================================================================
// MatrixCuda类成员函数实现
// ============================================================================
// 构造函数和析构函数
template <typename T>
CUDA_HOST MatrixCuda<T>::MatrixCuda()
: rows_(0), cols_(0), device_data_(nullptr),
device_allocated_(false), host_modified_(false), device_modified_(false) {}
template <typename T>
CUDA_HOST MatrixCuda<T>::MatrixCuda(long rows, long cols)
: rows_(rows), cols_(cols), device_data_(nullptr),
device_allocated_(false), host_modified_(false), device_modified_(false) {
if (rows <= 0 || cols <= 0) {
throw std::invalid_argument("Matrix dimensions must be positive");
}
host_data_.resize(rows * cols, T(0));
}
template <typename T>
CUDA_HOST MatrixCuda<T>::MatrixCuda(long rows, long cols, const std::vector<T>& data)
: rows_(rows), cols_(cols), device_data_(nullptr),
device_allocated_(false), host_modified_(false), device_modified_(false) {
initialize_from_vector(data);
}
template <typename T>
CUDA_HOST MatrixCuda<T>::MatrixCuda(const MatrixCuda<T>& other)
: rows_(other.rows_), cols_(other.cols_),
host_data_(other.host_data_), device_data_(nullptr),
device_allocated_(false), host_modified_(other.host_modified_),
device_modified_(false) {
if (other.device_allocated_) {
allocate_device_memory();
copy_to_device();
}
}
template <typename T>
CUDA_HOST MatrixCuda<T>::MatrixCuda(MatrixCuda<T>&& other) noexcept
: rows_(other.rows_), cols_(other.cols_),
host_data_(std::move(other.host_data_)),
device_data_(other.device_data_),
device_allocated_(other.device_allocated_),
host_modified_(other.host_modified_),
device_modified_(other.device_modified_) {
other.rows_ = 0;
other.cols_ = 0;
other.device_data_ = nullptr;
other.device_allocated_ = false;
other.host_modified_ = false;
other.device_modified_ = false;
}
template <typename T>
CUDA_HOST MatrixCuda<T>::~MatrixCuda() {
free_device_memory();
}
// 赋值运算符
template <typename T>
CUDA_HOST MatrixCuda<T>& MatrixCuda<T>::operator=(const MatrixCuda<T>& other) {
if (this != &other) {
free_device_memory();
rows_ = other.rows_;
cols_ = other.cols_;
host_data_ = other.host_data_;
host_modified_ = other.host_modified_;
device_modified_ = false;
if (other.device_allocated_) {
allocate_device_memory();
copy_to_device();
}
}
return *this;
}
template <typename T>
CUDA_HOST MatrixCuda<T>& MatrixCuda<T>::operator=(MatrixCuda<T>&& other) noexcept {
if (this != &other) {
free_device_memory();
rows_ = other.rows_;
cols_ = other.cols_;
host_data_ = std::move(other.host_data_);
device_data_ = other.device_data_;
device_allocated_ = other.device_allocated_;
host_modified_ = other.host_modified_;
device_modified_ = other.device_modified_;
other.rows_ = 0;
other.cols_ = 0;
other.device_data_ = nullptr;
other.device_allocated_ = false;
other.host_modified_ = false;
other.device_modified_ = false;
}
return *this;
}
// 内存管理函数
template <typename T>
CUDA_HOST void MatrixCuda<T>::allocate_device_memory() {
if (device_allocated_ || get_total_elements() == 0) {
return;
}
cudaError_t err = cudaMalloc(&device_data_, get_total_elements() * sizeof(T));
CHECK_CUDA_ERROR(err);
device_allocated_ = true;
device_modified_ = false;
}
template <typename T>
CUDA_HOST void MatrixCuda<T>::free_device_memory() {
if (device_allocated_ && device_data_ != nullptr) {
cudaError_t err = cudaFree(device_data_);
CHECK_CUDA_ERROR(err);
device_data_ = nullptr;
device_allocated_ = false;
device_modified_ = false;
}
}
template <typename T>
CUDA_HOST void MatrixCuda<T>::copy_to_device() {
if (!device_allocated_) {
allocate_device_memory();
}
if (host_modified_ && get_total_elements() > 0) {
cudaError_t err = cudaMemcpy(device_data_, host_data_.data(),
get_total_elements() * sizeof(T),
cudaMemcpyHostToDevice);
CHECK_CUDA_ERROR(err);
host_modified_ = false;
device_modified_ = true;
}
}
template <typename T>
CUDA_HOST void MatrixCuda<T>::copy_from_device() {
if (device_allocated_ && device_modified_ && get_total_elements() > 0) {
cudaError_t err = cudaMemcpy(host_data_.data(), device_data_,
get_total_elements() * sizeof(T),
cudaMemcpyDeviceToHost);
CHECK_CUDA_ERROR(err);
device_modified_ = false;
host_modified_ = true;
}
}
template <typename T>
CUDA_HOST void MatrixCuda<T>::sync_device_to_host() {
copy_from_device();
}
template <typename T>
CUDA_HOST void MatrixCuda<T>::sync_host_to_device() {
copy_to_device();
}
// 访问器函数
template <typename T>
CUDA_HOST T MatrixCuda<T>::get_element(long row, long col) const {
check_index(row, col);
return host_data_[row * cols_ + col];
}
template <typename T>
CUDA_HOST void MatrixCuda<T>::set_element(long row, long col, T value) {
check_index(row, col);
host_data_[row * cols_ + col] = value;
host_modified_ = true;
}
// 形状检查函数
template <typename T>
CUDA_HOST bool MatrixCuda<T>::has_same_shape(const MatrixCuda<T>& other) const {
return rows_ == other.rows_ && cols_ == other.cols_;
}
template <typename T>
CUDA_HOST bool MatrixCuda<T>::has_dot_shape(const MatrixCuda<T>& other) const {
return cols_ == other.rows_;
}
template <typename T>
CUDA_HOST void MatrixCuda<T>::assert_same_shape(const MatrixCuda<T>& other) const {
if (!has_same_shape(other)) {
throw std::invalid_argument("Matrices must have the same shape");
}
}
template <typename T>
CUDA_HOST void MatrixCuda<T>::assert_dot_shape(const MatrixCuda<T>& other) const {
if (!has_dot_shape(other)) {
throw std::invalid_argument("Matrices cannot be multiplied: cols of first != rows of second");
}
}
// 矩阵运算函数
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::add(const MatrixCuda<T>& other) const {
assert_same_shape(other);
MatrixCuda<T> result(rows_, cols_);
// 确保数据在设备上
sync_host_to_device();
other.sync_host_to_device();
result.allocate_device_memory();
// 设置CUDA内核参数
long total_elements = get_total_elements();
dim3 block_size = get_block_size(total_elements);
dim3 grid_size = get_grid_size(total_elements, block_size);
// 启动内核
cuda_kernels::matrix_add_kernel<T><<<grid_size, block_size>>>(
device_data_, other.device_data_, result.device_data_,
rows_, cols_, total_elements
);
CHECK_CUDA_ERROR(cudaGetLastError());
CHECK_CUDA_ERROR(cudaDeviceSynchronize());
result.device_modified_ = true;
result.sync_device_to_host();
return result;
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::subtract(const MatrixCuda<T>& other) const {
assert_same_shape(other);
MatrixCuda<T> result(rows_, cols_);
// 确保数据在设备上
sync_host_to_device();
other.sync_host_to_device();
result.allocate_device_memory();
// 设置CUDA内核参数
long total_elements = get_total_elements();
dim3 block_size = get_block_size(total_elements);
dim3 grid_size = get_grid_size(total_elements, block_size);
// 启动内核
cuda_kernels::matrix_sub_kernel<T><<<grid_size, block_size>>>(
device_data_, other.device_data_, result.device_data_,
rows_, cols_, total_elements
);
CHECK_CUDA_ERROR(cudaGetLastError());
CHECK_CUDA_ERROR(cudaDeviceSynchronize());
result.device_modified_ = true;
result.sync_device_to_host();
return result;
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::multiply_scalar(T scalar) const {
MatrixCuda<T> result(rows_, cols_);
// 确保数据在设备上
sync_host_to_device();
result.allocate_device_memory();
// 设置CUDA内核参数
long total_elements = get_total_elements();
dim3 block_size = get_block_size(total_elements);
dim3 grid_size = get_grid_size(total_elements, block_size);
// 启动内核
cuda_kernels::matrix_scalar_mul_kernel<T><<<grid_size, block_size>>>(
device_data_, scalar, result.device_data_, total_elements
);
CHECK_CUDA_ERROR(cudaGetLastError());
CHECK_CUDA_ERROR(cudaDeviceSynchronize());
result.device_modified_ = true;
result.sync_device_to_host();
return result;
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::add_scalar(T scalar) const {
MatrixCuda<T> result(rows_, cols_);
// 确保数据在设备上
sync_host_to_device();
result.allocate_device_memory();
// 设置CUDA内核参数
long total_elements = get_total_elements();
dim3 block_size = get_block_size(total_elements);
dim3 grid_size = get_grid_size(total_elements, block_size);
// 启动内核
cuda_kernels::matrix_scalar_add_kernel<T><<<grid_size, block_size>>>(
device_data_, scalar, result.device_data_, total_elements
);
CHECK_CUDA_ERROR(cudaGetLastError());
CHECK_CUDA_ERROR(cudaDeviceSynchronize());
result.device_modified_ = true;
result.sync_device_to_host();
return result;
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::transpose() const {
MatrixCuda<T> result(cols_, rows_);
// 确保数据在设备上
sync_host_to_device();
result.allocate_device_memory();
// 设置CUDA内核参数(2D网格用于转置)
dim3 block_size(16, 16);
dim3 grid_size((cols_ + block_size.x - 1) / block_size.x,
(rows_ + block_size.y - 1) / block_size.y);
// 启动转置内核
cuda_kernels::matrix_transpose_kernel<T><<<grid_size, block_size>>>(
device_data_, result.device_data_, rows_, cols_
);
CHECK_CUDA_ERROR(cudaGetLastError());
CHECK_CUDA_ERROR(cudaDeviceSynchronize());
result.device_modified_ = true;
result.sync_device_to_host();
return result;
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::hadamard_product(const MatrixCuda<T>& other) const {
assert_same_shape(other);
MatrixCuda<T> result(rows_, cols_);
// 确保数据在设备上
sync_host_to_device();
other.sync_host_to_device();
result.allocate_device_memory();
// 设置CUDA内核参数
long total_elements = get_total_elements();
dim3 block_size = get_block_size(total_elements);
dim3 grid_size = get_grid_size(total_elements, block_size);
// 启动内核
cuda_kernels::matrix_hadamard_kernel<T><<<grid_size, block_size>>>(
device_data_, other.device_data_, result.device_data_, total_elements
);
CHECK_CUDA_ERROR(cudaGetLastError());
CHECK_CUDA_ERROR(cudaDeviceSynchronize());
result.device_modified_ = true;
result.sync_device_to_host();
return result;
}
// 运算符重载
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::operator+(const MatrixCuda<T>& other) const {
return add(other);
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::operator+(T scalar) const {
return add_scalar(scalar);
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::operator-(const MatrixCuda<T>& other) const {
return subtract(other);
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::operator-(T scalar) const {
return add_scalar(-scalar);
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::operator*(T scalar) const {
return multiply_scalar(scalar);
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::operator/(T scalar) const {
if (scalar == T(0)) {
throw std::invalid_argument("Division by zero");
}
return multiply_scalar(T(1) / scalar);
}
// 复合赋值运算符
template <typename T>
CUDA_HOST MatrixCuda<T>& MatrixCuda<T>::operator+=(const MatrixCuda<T>& other) {
*this = *this + other;
return *this;
}
template <typename T>
CUDA_HOST MatrixCuda<T>& MatrixCuda<T>::operator+=(T scalar) {
*this = *this + scalar;
return *this;
}
template <typename T>
CUDA_HOST MatrixCuda<T>& MatrixCuda<T>::operator-=(const MatrixCuda<T>& other) {
*this = *this - other;
return *this;
}
template <typename T>
CUDA_HOST MatrixCuda<T>& MatrixCuda<T>::operator-=(T scalar) {
*this = *this - scalar;
return *this;
}
template <typename T>
CUDA_HOST MatrixCuda<T>& MatrixCuda<T>::operator*=(T scalar) {
*this = *this * scalar;
return *this;
}
template <typename T>
CUDA_HOST MatrixCuda<T>& MatrixCuda<T>::operator/=(T scalar) {
*this = *this / scalar;
return *this;
}
// 矩阵乘法(基础实现,使用朴素算法)
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::matmul(const MatrixCuda<T>& other) const {
assert_dot_shape(other);
MatrixCuda<T> result(rows_, other.cols_);
// 同步数据到主机(使用朴素算法在主机上计算)
sync_device_to_host();
other.sync_device_to_host();
// 朴素矩阵乘法(O(n³))
for (long i = 0; i < rows_; ++i) {
for (long j = 0; j < other.cols_; ++j) {
T sum = T(0);
for (long k = 0; k < cols_; ++k) {
sum += get_element(i, k) * other.get_element(k, j);
}
result.set_element(i, j, sum);
}
}
// 标记结果已修改
result.host_modified_ = true;
result.sync_host_to_device();
return result;
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::operator*(const MatrixCuda<T>& other) const {
return matmul(other);
}
// 工具函数
template <typename T>
CUDA_HOST void MatrixCuda<T>::print(const std::string& name) const {
sync_device_to_host();
if (!name.empty()) {
std::cout << name << " (" << rows_ << "x" << cols_ << "):" << std::endl;
} else {
std::cout << "Matrix (" << rows_ << "x" << cols_ << "):" << std::endl;
}
for (long i = 0; i < rows_; ++i) {
std::cout << " [";
for (long j = 0; j < cols_; ++j) {
std::cout << get_element(i, j);
if (j < cols_ - 1) std::cout << ", ";
}
std::cout << "]" << std::endl;
}
}
template <typename T>
CUDA_HOST void MatrixCuda<T>::fill(T value) {
std::fill(host_data_.begin(), host_data_.end(), value);
host_modified_ = true;
}
template <typename T>
CUDA_HOST void MatrixCuda<T>::fill_random(T min, T max) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<T> dist(min, max);
for (auto& val : host_data_) {
val = dist(gen);
}
host_modified_ = true;
}
template <typename T>
CUDA_HOST bool MatrixCuda<T>::is_valid() const {
return rows_ > 0 && cols_ > 0 && !host_data_.empty();
}
// 静态工厂方法
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::zeros(long rows, long cols) {
MatrixCuda<T> mat(rows, cols);
mat.fill(T(0));
return mat;
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::ones(long rows, long cols) {
MatrixCuda<T> mat(rows, cols);
mat.fill(T(1));
return mat;
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::identity(long size) {
MatrixCuda<T> mat(size, size);
mat.fill(T(0));
for (long i = 0; i < size; ++i) {
mat.set_element(i, i, T(1));
}
return mat;
}
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::random(long rows, long cols, T min, T max) {
MatrixCuda<T> mat(rows, cols);
mat.fill_random(min, max);
return mat;
}
// 从现有Matrx类转换
template <typename T>
CUDA_HOST MatrixCuda<T> MatrixCuda<T>::from_matrx(const class Matrx<T>& mat) {
// 注意:这里假设Matrx类有get_row(), get_col(), get_data()方法
long rows = mat.get_row();
long cols = mat.get_col();
const std::vector<T>& data = mat.get_data();
return MatrixCuda<T>(rows, cols, data);
}
template <typename T>
CUDA_HOST class Matrx<T> MatrixCuda<T>::to_matrx() const {
sync_device_to_host();
// 注意:这里假设Matrx类有相应的构造函数
// 实际实现可能需要根据Matrx类的具体接口调整
return class Matrx<T>(rows_, cols_, host_data_);
}
// 私有辅助函数
template <typename T>
CUDA_HOST void MatrixCuda<T>::check_index(long row, long col) const {
if (row < 0 || row >= rows_ || col < 0 || col >= cols_) {
throw std::out_of_range("Matrix index out of range");
}
}
template <typename T>
CUDA_HOST void MatrixCuda<T>::initialize_from_vector(const std::vector<T>& data) {
if (data.size() != static_cast<size_t>(rows_ * cols_)) {
throw std::invalid_argument("Data size does not match matrix dimensions");
}
host_data_ = data;
host_modified_ = true;
}
// CUDA错误检查
template <typename T>
CUDA_HOST void MatrixCuda<T>::check_cuda_error(cudaError_t error, const char* file, int line) {
if (error != cudaSuccess) {
std::cerr << "CUDA error at " << file << ":" << line << ": "
<< cudaGetErrorString(error) << std::endl;
throw std::runtime_error("CUDA error occurred");
}
}
// 计算线程块和网格大小
template <typename T>
CUDA_HOST dim3 MatrixCuda<T>::get_block_size(long total_elements) {
// 使用256个线程的块(CUDA的典型值)
const long max_threads_per_block = 256;
long threads = std::min(total_elements, max_threads_per_block);
return dim3(threads, 1, 1);
}
template <typename T>
CUDA_HOST dim3 MatrixCuda<T>::get_grid_size(long total_elements, dim3 block_size) {
// 计算需要的块数
long blocks = (total_elements + block_size.x - 1) / block_size.x;
// 限制最大块数(避免过多块)
const long max_blocks = 65535;
blocks = std::min(blocks, max_blocks);
return dim3(blocks, 1, 1);
}
// 显式实例化
template class MatrixCuda<float>;
template class MatrixCuda<double>;
} // namespace qing
#endif // MATRIX_CUDA_INL_CUH
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#include <iostream>
#include <stdexcept>
#include "nn/NeuralNetwork.hpp"
namespace qing {
class NNBuilder { /* 神经网络构建 */
public:
void add(NeuralNetwork &layer);
/* 获取变换层的形状
* FIXME: 这个函数有必要吗 */
std::vector<std::pair<long, long>> get_shape() const;
/* 向标准输出打印形状 */
void print_shape() const;
/* 向输出流储存模型 */
void save(std::ostream& out) {
for (auto& layer: nn) {
layer.save(out);
}
}
/* 从输入流加载模型 */
void load(std::istream& in) {
nn.clear();
while(true) {
try{
auto layer = NeuralNetwork::Load_in_Factory(in);
add(layer);
}
catch (std::runtime_error& e) {
break;
}
}
}
/*
* 将一层非线性层分裂为两层,相当于插入一层神经元
*/
//void fork(int index, long num, float rate) {
/*
* 神经网络生长,增加第layer层,每层增加num个神经元
*
* layer: 增加的层数
* num: 增加的神经元数量
*
* FIXME: 神经网络生长时应当冻结其它层(除了最后一层?)(learning_rate == 0)
*/
//void grow(int layer, int num) {
/* 前向反馈 */
std::vector<float> forward(std::vector<float>& r);
/* 反向传播 */
std::vector<float> backward(std::vector<float>& errs, float discount = 1.0);
/* 计算误差 */
std::vector<float> cal_err(std::vector<float>& r, std::vector<float>& t);
private:
std::vector<NeuralNetwork> nn;
};
}
+149
View File
@@ -0,0 +1,149 @@
#pragma once
#include <random>
#include "nn/Matrix.hh"
#include "nn/ILayerFullyConnectted.hpp"
namespace qing {
/* 人工神经网络 - 全连接层 */
class NeuralNetwork: public ILayerFullyConnectted {
public:
/* 构造函数 使用基本参数构造
* FIXME: 只能输入已经存在的向量 */
NeuralNetwork(
const long inputno, const long outputno,
const std::vector<float> &weights,
const std::vector<float> &bias,
const float learning_rate,
const ActivationFunc func_type
);
/* 获取激活函数枚举类型 */
const ActivationFunc& get_f() const {
return f_type;
}
/* 获取学习率 */
const float& get_lr () const {
return learning_rate;
}
/*
* 获取输入层大小
*
* FIXME: 优化掉这两个访问器,通过直接返回矩阵,获取层的形状
*/
long get_inputno() const;
/* 获取输出层大小 */
long get_outputno() const;
/* 获取权重矩阵 */
Matrx<float>& get_weights();
/* 获取偏置矩阵 */
Matrx<float>& get_bias();
/* 正态分布Xavier初始化 */
static void xavierNormalInit(std::vector<float> &weights, long fan_in, long fan_out);
/* 均匀分布Xavier初始化函数 */
static void xavierUniformInit(std::vector<float> &weights, int fan_in, int fan_out);
/* 工厂模式 通过形状构建全连接层 */
static NeuralNetwork Create_in_Factory(const long inputno, const long outputno, float learning_rate, ActivationFunc func_type);
/* 工厂模式 通过输入流读形状构建 */
static NeuralNetwork Load_in_Factory(std::istream& in) {
std::string f_name;
if (!(in >> f_name))
throw std::runtime_error("read out of lines");
float learning_rate;
in >> learning_rate;
/*------------------*/
size_t row, col;
in >> row; /* FIXME: 如果是负数会怎样 */
in >> col;
auto weis = load_vec(in, row * col);
size_t rowb, colb;
in >> rowb >> colb; /* FIXME: 如果这里读错了,也要抛出异常 */
auto bias = load_vec(in, col);
auto layer = NeuralNetwork(
row, col, weis, bias, learning_rate, ILayer::parse_ff_type(f_name));
/*------------------*/
return layer;
}
static std::vector<float> load_vec(std::istream& in, size_t length) {
std::vector<float> data;
for (size_t i = 0; i < length; ++i) {
float item;
in >> item;
data.push_back(item);
}
return data;
}
/* 前向反馈 */
std::vector<float> forward(const std::vector<float>& x) override;
/* 反向传播 */
std::vector<float> backward(const std::vector<float>& errors) override;
/*
* 更新权重
*
* 可以输入一个折扣系数
*/
void update(float discount = 1.0);
/*
* 神经网络层长大
*
* 重新初始化权重矩阵,使用均匀分布初始化
*
* num: 增加的神经元数量
* flag: true为增加输入层,false为增加输出层
*/
//void grow(int num, bool flag) {
/* 输出神经网络到输出流 */
void save(std::ostream& out) override {
out << get_f_type(f_type) << " ";
out << learning_rate << " ";
weights.save(out); out << " ";
bias.save(out); out << "\n";
}
/* 从输入流加载模型
* FIXME: 只能从工厂模式中构造 */
//void load(std::istream& in) override
private:
/* 神经网络层规格 */
long inputno, outputno;
/* 权重与偏置矩阵 */
Matrx<float> weights; /* (outputs, inputs) */
Matrx<float> bias; /* (1, outputs) */
/* 学习率 */
float learning_rate;
/* 激活函数 */
ActivationFunc f_type;
/* 存储在类内部的上一次的输入,输出,和激活前输出 */
Matrx<float> prev_inputs, prev_raw_inputs, prev_outputs;
/* 存储本次学习的权重梯度以及偏置梯度 */
Matrx<float> weights_grad, bias_grad;
};
}
+502
View File
@@ -0,0 +1,502 @@
#pragma once
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <limits>
#include <functional>
class PoolingLayer {
private:
// 池化类型枚举
enum class PoolType {
MAX,
AVG
};
// 参数
int pool_height;
int pool_width;
int stride;
int padding;
PoolType pool_type;
// 输入输出尺寸
int input_channels;
int input_height;
int input_width;
int output_height;
int output_width;
// 中间变量缓存(用于反向传播)
std::vector<int> max_idx_cache; // 最大池化时缓存最大值位置
std::vector<float> input_cache; // 缓存的输入
std::vector<float> output_cache; // 缓存的输出
// 辅助函数
//void max_pool_forward(const float* input, float* output, int batch_size);
//void avg_pool_forward(const float* input, float* output, int batch_size);
//void max_pool_backward(const float* grad_output, float* grad_input, int batch_size);
//void avg_pool_backward(const float* grad_output, float* grad_input, int batch_size);
public:
// 构造函数
PoolingLayer(int pool_size, int stride = 2, int padding = 0, bool max_pool = true)
: pool_height(pool_size),
pool_width(pool_size),
stride(stride),
padding(padding),
pool_type(max_pool ? PoolType::MAX : PoolType::AVG) {}
// 带不同高度和宽度的构造函数
PoolingLayer(int pool_height, int pool_width, int stride = 2, int padding = 0, bool max_pool = true)
: pool_height(pool_height),
pool_width(pool_width),
stride(stride),
padding(padding),
pool_type(max_pool ? PoolType::MAX : PoolType::AVG) {}
// 设置输入尺寸
void set_input_shape(int channels, int height, int width) {
input_channels = channels;
input_height = height;
input_width = width;
// 计算输出尺寸
output_height = (height + 2 * padding - pool_height) / stride + 1;
output_width = (width + 2 * padding - pool_width) / stride + 1;
// 清空缓存
max_idx_cache.clear();
input_cache.clear();
output_cache.clear();
}
// 前向传播
std::vector<float> forward(const std::vector<float>& input, int batch_size = 1, bool training = true) {
// 计算输出大小
int output_size = batch_size * input_channels * output_height * output_width;
std::vector<float> output(output_size, 0.0f);
// 如果训练模式,缓存输入
if (training) {
input_cache = input;
}
// 根据池化类型选择前向传播函数
if (pool_type == PoolType::MAX) {
max_pool_forward(input.data(), output.data(), batch_size);
} else {
avg_pool_forward(input.data(), output.data(), batch_size);
}
// 如果训练模式,缓存输出
if (training) {
output_cache = output;
}
return output;
}
// 反向传播
std::vector<float> backward(const std::vector<float>& grad_output, int batch_size = 1) {
// 创建输入梯度
std::vector<float> grad_input(batch_size * input_channels * input_height * input_width, 0.0f);
// 根据池化类型选择反向传播函数
if (pool_type == PoolType::MAX) {
max_pool_backward(grad_output.data(), grad_input.data(), batch_size);
} else {
avg_pool_backward(grad_output.data(), grad_input.data(), batch_size);
}
return grad_input;
}
// 获取输出尺寸
std::tuple<int, int, int> get_output_shape() const {
return std::make_tuple(input_channels, output_height, output_width);
}
// 获取参数信息
void print_info() const {
std::cout << "Pooling Layer Info:" << std::endl;
std::cout << " Type: " << (pool_type == PoolType::MAX ? "Max Pooling" : "Average Pooling") << std::endl;
std::cout << " Pool Size: " << pool_height << "x" << pool_width << std::endl;
std::cout << " Stride: " << stride << std::endl;
std::cout << " Padding: " << padding << std::endl;
std::cout << " Input Shape: " << input_channels << "x" << input_height << "x" << input_width << std::endl;
std::cout << " Output Shape: " << input_channels << "x" << output_height << "x" << output_width << std::endl;
}
private:
// 最大池化前向传播实现
void max_pool_forward(const float* input, float* output, int batch_size) {
// 计算填充后的尺寸
int padded_height = input_height + 2 * padding;
int padded_width = input_width + 2 * padding;
// 清空最大值位置缓存
max_idx_cache.clear();
max_idx_cache.resize(batch_size * input_channels * output_height * output_width, -1);
// 对每个批次、每个通道进行处理
for (int b = 0; b < batch_size; b++) {
for (int c = 0; c < input_channels; c++) {
// 当前通道的输入和输出指针
const float* input_ptr = input + b * input_channels * input_height * input_width +
c * input_height * input_width;
float* output_ptr = output + b * input_channels * output_height * output_width +
c * output_height * output_width;
int* max_idx_ptr = max_idx_cache.data() + b * input_channels * output_height * output_width +
c * output_height * output_width;
// 对每个输出位置进行计算
for (int oh = 0; oh < output_height; oh++) {
for (int ow = 0; ow < output_width; ow++) {
// 计算池化窗口的起始位置
int start_h = oh * stride - padding;
int start_w = ow * stride - padding;
// 计算池化窗口的结束位置
int end_h = std::min(start_h + pool_height, padded_height);
int end_w = std::min(start_w + pool_width, padded_width);
// 调整起始位置,确保不超出边界
start_h = std::max(start_h, 0);
start_w = std::max(start_w, 0);
// 在池化窗口内寻找最大值
float max_val = -std::numeric_limits<float>::max();
int max_idx = -1;
for (int ph = start_h; ph < end_h; ph++) {
for (int pw = start_w; pw < end_w; pw++) {
// 计算原始输入中的索引(考虑填充)
int orig_ph = ph - padding;
int orig_pw = pw - padding;
// 检查是否在有效范围内
if (orig_ph >= 0 && orig_ph < input_height &&
orig_pw >= 0 && orig_pw < input_width) {
int idx = orig_ph * input_width + orig_pw;
float val = input_ptr[idx];
if (val > max_val) {
max_val = val;
max_idx = idx;
}
}
}
}
// 存储输出和最大值位置
output_ptr[oh * output_width + ow] = max_val;
max_idx_ptr[oh * output_width + ow] = max_idx;
}
}
}
}
}
// 平均池化前向传播实现
void avg_pool_forward(const float* input, float* output, int batch_size) {
// 计算填充后的尺寸
int padded_height = input_height + 2 * padding;
int padded_width = input_width + 2 * padding;
// 对每个批次、每个通道进行处理
for (int b = 0; b < batch_size; b++) {
for (int c = 0; c < input_channels; c++) {
// 当前通道的输入和输出指针
const float* input_ptr = input + b * input_channels * input_height * input_width +
c * input_height * input_width;
float* output_ptr = output + b * input_channels * output_height * output_width +
c * output_height * output_width;
// 对每个输出位置进行计算
for (int oh = 0; oh < output_height; oh++) {
for (int ow = 0; ow < output_width; ow++) {
// 计算池化窗口的起始位置
int start_h = oh * stride - padding;
int start_w = ow * stride - padding;
// 计算池化窗口的结束位置
int end_h = std::min(start_h + pool_height, padded_height);
int end_w = std::min(start_w + pool_width, padded_width);
// 调整起始位置,确保不超出边界
start_h = std::max(start_h, 0);
start_w = std::max(start_w, 0);
// 在池化窗口内计算平均值
float sum = 0.0f;
int count = 0;
for (int ph = start_h; ph < end_h; ph++) {
for (int pw = start_w; pw < end_w; pw++) {
// 计算原始输入中的索引(考虑填充)
int orig_ph = ph - padding;
int orig_pw = pw - padding;
// 检查是否在有效范围内
if (orig_ph >= 0 && orig_ph < input_height &&
orig_pw >= 0 && orig_pw < input_width) {
int idx = orig_ph * input_width + orig_pw;
sum += input_ptr[idx];
count++;
}
}
}
// 计算平均值
output_ptr[oh * output_width + ow] = (count > 0) ? sum / count : 0.0f;
}
}
}
}
}
// 最大池化反向传播实现
void max_pool_backward(const float* grad_output, float* grad_input, int batch_size) {
// 初始化输入梯度为0
std::memset(grad_input, 0, batch_size * input_channels * input_height * input_width * sizeof(float));
// 对每个批次、每个通道进行处理
for (int b = 0; b < batch_size; b++) {
for (int c = 0; c < input_channels; c++) {
// 当前通道的梯度指针
const float* grad_output_ptr = grad_output + b * input_channels * output_height * output_width +
c * output_height * output_width;
float* grad_input_ptr = grad_input + b * input_channels * input_height * input_width +
c * input_height * input_width;
const int* max_idx_ptr = max_idx_cache.data() + b * input_channels * output_height * output_width +
c * output_height * output_width;
// 对每个输出位置进行反向传播
for (int oh = 0; oh < output_height; oh++) {
for (int ow = 0; ow < output_width; ow++) {
// 获取最大值位置索引
int max_idx = max_idx_ptr[oh * output_width + ow];
// 如果最大值位置有效,将梯度传递到该位置
if (max_idx >= 0) {
grad_input_ptr[max_idx] += grad_output_ptr[oh * output_width + ow];
}
}
}
}
}
}
// 平均池化反向传播实现
void avg_pool_backward(const float* grad_output, float* grad_input, int batch_size) {
// 初始化输入梯度为0
std::memset(grad_input, 0, batch_size * input_channels * input_height * input_width * sizeof(float));
// 计算填充后的尺寸
int padded_height = input_height + 2 * padding;
int padded_width = input_width + 2 * padding;
// 对每个批次、每个通道进行处理
for (int b = 0; b < batch_size; b++) {
for (int c = 0; c < input_channels; c++) {
// 当前通道的梯度指针
const float* grad_output_ptr = grad_output + b * input_channels * output_height * output_width +
c * output_height * output_width;
float* grad_input_ptr = grad_input + b * input_channels * input_height * input_width +
c * input_height * input_width;
// 对每个输出位置进行反向传播
for (int oh = 0; oh < output_height; oh++) {
for (int ow = 0; ow < output_width; ow++) {
// 计算池化窗口的起始位置
int start_h = oh * stride - padding;
int start_w = ow * stride - padding;
// 计算池化窗口的结束位置
int end_h = std::min(start_h + pool_height, padded_height);
int end_w = std::min(start_w + pool_width, padded_width);
// 调整起始位置,确保不超出边界
start_h = std::max(start_h, 0);
start_w = std::max(start_w, 0);
// 计算有效池化窗口的大小
int count = 0;
for (int ph = start_h; ph < end_h; ph++) {
for (int pw = start_w; pw < end_w; pw++) {
// 计算原始输入中的索引(考虑填充)
int orig_ph = ph - padding;
int orig_pw = pw - padding;
// 检查是否在有效范围内
if (orig_ph >= 0 && orig_ph < input_height &&
orig_pw >= 0 && orig_pw < input_width) {
count++;
}
}
}
// 如果池化窗口内有有效元素,计算平均梯度
if (count > 0) {
float avg_grad = grad_output_ptr[oh * output_width + ow] / count;
// 将平均梯度分配到池化窗口内的每个位置
for (int ph = start_h; ph < end_h; ph++) {
for (int pw = start_w; pw < end_w; pw++) {
// 计算原始输入中的索引(考虑填充)
int orig_ph = ph - padding;
int orig_pw = pw - padding;
// 检查是否在有效范围内
if (orig_ph >= 0 && orig_ph < input_height &&
orig_pw >= 0 && orig_pw < input_width) {
int idx = orig_ph * input_width + orig_pw;
grad_input_ptr[idx] += avg_grad;
}
}
}
}
}
}
}
}
}
};
// 使用示例函数
inline void pooling_layer_example() {
std::cout << "=== Pooling Layer Example ===" << std::endl;
// 创建最大池化层
PoolingLayer max_pool(2, 2, 0, true); // 2x2 最大池化,步长2,无填充
max_pool.set_input_shape(3, 4, 4); // 3个通道,4x4输入
// 创建输入数据
std::vector<float> input(3 * 4 * 4);
for (int i = 0; i < 3 * 4 * 4; i++) {
input[i] = static_cast<float>(i) / 10.0f;
}
// 前向传播
std::cout << "Forward pass..." << std::endl;
auto output = max_pool.forward(input, 1, true);
// 获取输出形状
auto [channels, height, width] = max_pool.get_output_shape();
std::cout << "Output shape: " << channels << "x" << height << "x" << width << std::endl;
// 打印输出
std::cout << "Output values (first 10): ";
for (int i = 0; i < std::min(10, static_cast<int>(output.size())); i++) {
std::cout << output[i] << " ";
}
std::cout << std::endl;
// 创建梯度(假设全为1)
std::vector<float> grad_output(output.size(), 1.0f);
// 反向传播
std::cout << "Backward pass..." << std::endl;
auto grad_input = max_pool.backward(grad_output, 1);
std::cout << "Gradient input size: " << grad_input.size() << std::endl;
std::cout << "Gradient input values (first 10): ";
for (int i = 0; i < std::min(10, static_cast<int>(grad_input.size())); i++) {
std::cout << grad_input[i] << " ";
}
std::cout << std::endl;
// 打印层信息
max_pool.print_info();
}
// 全局平均池化层(常用于CNN分类网络的最后)
class GlobalAveragePooling {
private:
int input_channels;
int input_height;
int input_width;
std::vector<float> input_cache;
public:
// 设置输入尺寸
void set_input_shape(int channels, int height, int width) {
input_channels = channels;
input_height = height;
input_width = width;
}
// 前向传播
std::vector<float> forward(const std::vector<float>& input, int batch_size = 1, bool training = true) {
// 缓存输入(如果需要)
if (training) {
input_cache = input;
}
// 计算输出大小:每个通道一个值
int output_size = batch_size * input_channels;
std::vector<float> output(output_size, 0.0f);
// 对每个批次、每个通道计算平均值
int spatial_size = input_height * input_width;
for (int b = 0; b < batch_size; b++) {
for (int c = 0; c < input_channels; c++) {
const float* input_ptr = input.data() + b * input_channels * spatial_size + c * spatial_size;
float sum = 0.0f;
// 计算该通道所有空间位置的平均值
for (int i = 0; i < spatial_size; i++) {
sum += input_ptr[i];
}
output[b * input_channels + c] = sum / spatial_size;
}
}
return output;
}
// 反向传播
std::vector<float> backward(const std::vector<float>& grad_output, int batch_size = 1) {
// 创建输入梯度
int spatial_size = input_height * input_width;
std::vector<float> grad_input(batch_size * input_channels * spatial_size, 0.0f);
// 对每个批次、每个通道分配梯度
for (int b = 0; b < batch_size; b++) {
for (int c = 0; c < input_channels; c++) {
float grad = grad_output[b * input_channels + c] / spatial_size;
float* grad_input_ptr = grad_input.data() + b * input_channels * spatial_size + c * spatial_size;
// 将梯度均匀分配到所有空间位置
for (int i = 0; i < spatial_size; i++) {
grad_input_ptr[i] = grad;
}
}
}
return grad_input;
}
// 获取输出尺寸
std::tuple<int, int> get_output_shape() const {
return std::make_tuple(input_channels, 1);
}
};
/*
// 创建池化层
PoolingLayer pool(2, 2, 0, true); // 2x2最大池化,步长2,无填充
pool.set_input_shape(64, 28, 28); // 64通道,28x28输入
// 前向传播
auto output = pool.forward(input, batch_size, true);
// 反向传播
auto grad_input = pool.backward(grad_output, batch_size);
*/
+229
View File
@@ -0,0 +1,229 @@
#pragma once
#include <iostream>
#include <fstream>
#include <filesystem>
#include <vector>
#include <sys/stat.h>
namespace qing {
/* 子系统类 */
class Modules {
public:
/* 传入子系统的路径 然后验证是否存在 */
Modules(std::string& mods) {
this->home = mods;
if (!is_exist(this->home))
throw SubsystemNotExist(mods);
}
/*
* 获取子系统下某个模块的启动脚本路径
*
* NOTE: 防注入检查是必须的
*/
std::string GetInitp(std::string name) {
std::string ret = get_mod_init_path(name);
if (is_exist(ret)) {
return ret;
}
else {
throw InitScriptNotExist(ret);
}
}
/*
* 列出子系统下的所有模块
*
* 优化建议:做抽象层表示模块概念
*/
std::vector<std::string> ListAll() {
std::vector<std::string> folders;
try {
for (const auto& entry: std::filesystem::directory_iterator(this->home))
if (entry.is_directory())
folders.push_back(entry.path().filename().string());
}
catch(const std::filesystem::filesystem_error& e) {
ListModulesFailed(this->home, e.what());
}
return folders;
}
/* 在该子系统下创建一个初始模块 */
void CrtMod(std::string &name) {
crt_mod_dir(name);
init_mod_dir(name);
init_mod_man(name);
grd_priv(name);
}
/* 删除子系统下一个模块 */
void DelMod(std::string &name) {
std::string fullpath = get_mod_path(name);
deleteDirectory(fullpath);
}
/* 子系统异常模块 */
class Mods_Exp: public std::runtime_error {
public:
explicit Mods_Exp(const std::string& msg): std::runtime_error(msg) {
;
}
};
/* 子系统不存在异常 */
class SubsystemNotExist: public Mods_Exp {
public:
explicit SubsystemNotExist(const std::string &name): Mods_Exp("Target subsystem " + name + " not exists.") {
;
}
};
/* 启动脚本不存在异常 */
class InitScriptNotExist: public Mods_Exp {
public:
explicit InitScriptNotExist(const std::string &name): Mods_Exp("Target init script " + name + " not exists") {
;
}
};
/* 列举模块失败异常 */
class ListModulesFailed: public Mods_Exp {
public:
explicit ListModulesFailed(const std::string &name, const std::string &reson): Mods_Exp("Error when read subsystem <" + name + "> , because " + reson) {
;
}
};
/* 要创建的路径已经存在 */
class AlreadyExist: public Mods_Exp {
public:
explicit AlreadyExist(const std::string &name): Mods_Exp(name) {
;
}
};
/* 创建失败 */
class CreateFailed: public Mods_Exp {
public:
explicit CreateFailed(const std::string &name): Mods_Exp(name) {
;
}
};
/* 提升权限失败 */
class GrandPrivilegesFailed: public Mods_Exp {
public:
explicit GrandPrivilegesFailed(const std::string &name): Mods_Exp(name) {
;
}
};
/* 删除失败 */
class DeleteFailed: public Mods_Exp {
public:
explicit DeleteFailed(const std::string &name): Mods_Exp(name) {
;
}
};
private:
/* 子系统的模块目录 */
std::string home;
/* 创建模块目录*/
void crt_mod_dir(std::string name) {
std::string fullpath = get_mod_path(name);
if (is_exist(fullpath)) {
throw AlreadyExist(name);
} else {
std::filesystem::create_directory(fullpath);
}
}
/* 初始化模块目录,创建启动脚本 */
void init_mod_dir(std::string name) {
std::string fullpath = get_mod_init_path(name);
if (is_exist(fullpath)) {
throw AlreadyExist(name);
}
else {
std::ofstream outFile = std::ofstream(fullpath);
if (outFile) {
outFile << "#!/bin/bash\n";
outFile << "cd \"$(dirname \"$0\")\" || exit\n\n";
outFile << "exec echo hello!";
} else {
throw CreateFailed(name);
}
}
}
/* 初始化一个模块的手册(README.md)*/
void init_mod_man(std::string name) {
std::string fullpath = get_mod_man_path(name);
if(is_exist(fullpath)) {
throw AlreadyExist(fullpath);
}
else {
std::ofstream outFile = std::ofstream(fullpath);
if (outFile) {
outFile << "# ";
outFile << name;
outFile << "\n";
} else {
throw CreateFailed(name);
}
}
}
/* 获取模块的路径 */
std::string get_mod_path(std::string &name) {
return this->home + get_sep() + name;
}
/* 获取模块的启动脚本路径 */
std::string get_mod_init_path(std::string &name) {
return get_mod_path(name) + get_sep()+ "_run.sh";
}
/* 获取模块的手册路径 */
std::string get_mod_man_path(std::string &name) {
return get_mod_path(name) + get_sep()+ "README.md";
}
/* 赋予一个文件755权限 */
void grd_priv(std::string &name) {
std::string fullpath = get_mod_init_path(name);
if (chmod(fullpath.c_str(), 0755) == -1) {
throw GrandPrivilegesFailed("path");
}
}
/* 获取当前可执行程序所在目录 */
static std::string get_curr() {
return std::filesystem::canonical("/proc/self/exe").parent_path().string();
}
/* 文件是否存在 */
static bool is_exist(std::string &path) {
return std::filesystem::exists(path);
}
/* 获取文件系统分隔符号 */
static char get_sep() {
return std::filesystem::path::preferred_separator;
}
/* 删除一整个文件夹 */
static void deleteDirectory(const std::filesystem::path& path) {
try { /* 递归 */
std::filesystem::remove_all(path) > 0;
}
catch (const std::filesystem::filesystem_error& e) {
throw DeleteFailed(e.what());
}
}
};
}