移植bot4cj
This commit is contained in:
1 parent
7803843155
commit
78ad3e6d57
76 files changed
+14468
No files matched your search
+263
@@ -0,0 +1,263 @@
|
||||
package bot4cj
|
||||
import std.io.*
|
||||
import std.math.*
|
||||
import std.convert.*
|
||||
import std.collection.*
|
||||
import bot4cj.math.*
|
||||
|
||||
/* -------- 矩阵编程语言 - 基本对象 --------
|
||||
* TODO:增加储存到文件系统的功能
|
||||
* TODO:神经网络层转换为矩阵对象
|
||||
-------- */
|
||||
class MatrixObject {
|
||||
private var name: String = "" /* -------- 名字 -------- */
|
||||
private var tmp = ArrayList<Rune>() /* ---- rune buf 字符缓冲区 ---- */
|
||||
private var grp = ArrayList<String>() /* ---- one line 整行缓冲区 ---- */
|
||||
private var values = ArrayList<ArrayList<String>>() /* ---- 原始矩阵 ---- */
|
||||
|
||||
/* -------- 是否已命名 -------- */
|
||||
public func hasName(): Bool { !(name == "") }
|
||||
|
||||
/* --------- 获得名字 --------- */
|
||||
public func getName(): String { return name }
|
||||
|
||||
/* ------- 取动作的长度 ------- */
|
||||
public mut prop size: Int64 {
|
||||
get() { values.size }
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
/* -------- 取动作的列数 -------- */
|
||||
public mut prop col: Int64 {
|
||||
get() {
|
||||
if (this.size > 0) {
|
||||
return values[0].size }
|
||||
else { return 0 }
|
||||
}
|
||||
|
||||
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
/* ------- 获取矩阵元素,只能取,不能修改 ------- */
|
||||
public operator func [](row: Int64, col: Int64): String {
|
||||
return values[row][col]
|
||||
}
|
||||
|
||||
/** -- 在某一行中寻找某一个字符串的列号 -- */
|
||||
public func indexOfCol(row: Int64, key: String): Int64 {
|
||||
for (col in 0..values[row].size) {
|
||||
if (values[row][col] == key) {
|
||||
return col }
|
||||
}
|
||||
|
||||
/* ---- 没找到 ---- */
|
||||
throw IllegalArgumentException("The key does not found.")
|
||||
}
|
||||
|
||||
/* ----- 储存对象 ----- */
|
||||
public func save(output: StringWriter<OutputStream>) {
|
||||
output.writeln("@${getName()}")
|
||||
for (vec in values) {
|
||||
for (item in vec) {
|
||||
output.write(item)
|
||||
output.write(" ")
|
||||
}
|
||||
output.writeln(";")
|
||||
}
|
||||
output.writeln("@")
|
||||
}
|
||||
|
||||
/* -------- 添加字符 -------- */
|
||||
public func addRune(c: Rune) { tmp.add(c) }
|
||||
|
||||
/* -------- 提交名字 -------- */
|
||||
public func submitName() {
|
||||
if (tmp.size == 0) { return }
|
||||
let s = String(tmp)
|
||||
tmp.clear()
|
||||
name = s
|
||||
}
|
||||
|
||||
/* -------- 提交元素 -------- */
|
||||
public func submitItem() {
|
||||
if (tmp.size == 0) { return }
|
||||
let s = String(tmp)
|
||||
tmp = ArrayList<Rune>()
|
||||
grp.add(s)
|
||||
}
|
||||
|
||||
/* -------- 提交行 -------- */
|
||||
public func submitLine(count: Int64) {
|
||||
if (grp.size == 0) { return }
|
||||
if (!values.size == 0 && !grp.size == values[values.size - 1].size)
|
||||
{ throw DoesNotSync(count) }
|
||||
values.add(grp)
|
||||
grp = ArrayList<String>()
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 对Nn1进行扩展 ---------------- */
|
||||
extend Nn1 {
|
||||
/* -------- 转换为MatrixObject对象 -------- */
|
||||
public func toMto(): MatrixObject {
|
||||
let mto = MatrixObject()
|
||||
return mto
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 矩阵式解析器 --------
|
||||
* TODO(20260514-qing): 这个类输入流数据,输出MatrixObject对象
|
||||
* 它是与其深度绑定的,我倾向于将其与MatrixObject融合
|
||||
* 即持有HashMap<String, ArrayList<ArrayList<String>>> */
|
||||
class MatrixParser {
|
||||
|
||||
/* ---- 静态判断Option<Rune>是否有值 ----
|
||||
- 该方法用于文本文件遍历时判空 */
|
||||
private static func hasValue(co: Option<Rune>): Bool {
|
||||
match(co) {
|
||||
case Some(_) => true
|
||||
case None => false
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 主初始化器(构造函数) --------
|
||||
- 设置目标输入流 */
|
||||
private var input: StringReader<InputStream>
|
||||
public init(input: StringReader<InputStream>) {
|
||||
this.input = input }
|
||||
|
||||
/* -------- 切换目标输入流 -------- */
|
||||
public func reset(input: StringReader<InputStream>) {
|
||||
this.input = input }
|
||||
|
||||
/* -------- 解析器 - 循环不变式 ----------------------
|
||||
* -------- 初始化: ss是一个动作脚本的输入流 --------
|
||||
* -------- 保持: ss是动作脚本剩余部分的输入流 ------
|
||||
* -------- 终止: 当输入流为空时,循环终止 ---------- */
|
||||
private var obj = MatrixObject()
|
||||
public func parse(): Option<MatrixObject> {
|
||||
var count = 1i64 /* 行计数器始于1 */
|
||||
flag = false /* 重置结束标志 */
|
||||
this.obj = MatrixObject() /* 创建一个新的对象,一次只解析一个对象 */
|
||||
var so = input.read() /* 获取初始状态 */
|
||||
while (!flag && hasValue(so)) {
|
||||
try {
|
||||
let s = so.getOrThrow()
|
||||
print(s) /* debug */
|
||||
count = _next(s, count) /* 进行解析解析 */
|
||||
so = input.read() /* 为下一次循环提供条件 */
|
||||
}
|
||||
catch (exp: BufOverTheLine) { throw exp }
|
||||
catch (exp: IllegalArgumentException) { throw IllegalArgumentException("Parse failed at line ${count}") }
|
||||
catch (exp: Exception) { throw UnknowExcept(count) } /* FIXME: 还是暴露出原来的错误位置来更好? */
|
||||
}
|
||||
|
||||
/* 如果没有新的矩阵式对象(意外终止)则返回空 */
|
||||
if (!obj.hasName()) { return None }
|
||||
return obj /* 否则为解析成功正常退出,返回解析到的矩阵式对象 */
|
||||
}
|
||||
|
||||
private var flag = false /* 结束标志 */
|
||||
private var nl: Bool = true /* new line */
|
||||
private var cm: Bool = false /* comment */
|
||||
private var nm: Bool = false /* name-line */
|
||||
private func _next(c: Rune, ct: Int64): Int64 {
|
||||
var count = ct
|
||||
/* ------------ COMMENTS ------------ */
|
||||
if (!cm && c == r'#') { cm = true } /* 注释开始时 */
|
||||
else if (cm && c == r'\n') { /* 注释结束时 */
|
||||
nl = true
|
||||
cm = false
|
||||
count += 1
|
||||
}
|
||||
else if (cm && c != r'\n') { ; } /* 注释进行时 */
|
||||
/* FIXME: EOF情况 */
|
||||
/* ------------ new line ------------ */
|
||||
else if (nl && (c == r' ' || c == r'\t' || c == r'\r')) { ; } /* 新行空字符,维持现状 */
|
||||
else if (nl && c == r'\n') { /* 新行空行 */
|
||||
count += 1
|
||||
}
|
||||
else if (nl && c == r'@' && !obj.hasName()) {
|
||||
nl = false
|
||||
nm = true
|
||||
}
|
||||
else if (nl && c == r'@' && obj.hasName()) {
|
||||
flag =true
|
||||
nl = false
|
||||
nm = true
|
||||
}
|
||||
else if (nl && c != r'@') { /* 新行内容 */
|
||||
nl = false
|
||||
obj.addRune(c)
|
||||
}
|
||||
/* ------------- not nl ------------- */
|
||||
else if (!nl && nm && (c == r' ' || c == r'\t' || c == r'\n')) { /* 添加名字 */
|
||||
obj.submitName()
|
||||
nl = true
|
||||
nm = false
|
||||
}
|
||||
else if (!nl && !nm && (c == r' ' || c == r'\t' || c == r"\n")) { /* 添加命名项 */
|
||||
obj.submitItem()
|
||||
if (c == r"\n") { count += 1 }
|
||||
}
|
||||
else if (!nl && c == r';') { /* 普通行结束 */
|
||||
obj.submitItem()
|
||||
obj.submitLine(count)
|
||||
nl = true
|
||||
}
|
||||
else if (!nl) { // 非空格、非制表符、非分号
|
||||
obj.addRune(c)
|
||||
}
|
||||
|
||||
/* -------- 返回行号 -------- */
|
||||
return count
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* -------- 矩阵式储存器 --------
|
||||
* - 接收矩阵式的哈希表(HashMap<String, MatrixObject>)
|
||||
* - 以及一个输出流(在构造函数)。 */
|
||||
class MatrixSaver {
|
||||
private let stringWriter: StringWriter<OutputStream>
|
||||
public init(output: OutputStream) {
|
||||
stringWriter = StringWriter(output) }
|
||||
public func save(C: LinkedList<MatrixObject>) {
|
||||
for (item in C) {
|
||||
item.save(stringWriter)
|
||||
stringWriter.writeln() /* 对象之间空一行 */
|
||||
stringWriter.flush() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------------------------------------- */
|
||||
/* --------------------------------------------------------- */
|
||||
/* TODO(20260516-qing): 继续完善异常种类 */
|
||||
|
||||
/* -------- 解析异常 -------- */
|
||||
open class ParseExcept <: Exception {
|
||||
public ParseExcept(msg: String, line: Int64) {
|
||||
super('${msg}. we got line: ${line}') }
|
||||
}
|
||||
|
||||
/* -------- 缓冲区过长 -------- */
|
||||
class BufOverTheLine <: ParseExcept {
|
||||
BufOverTheLine(count: Int64) {
|
||||
super('Buffer length over the line', count) }
|
||||
}
|
||||
|
||||
/* -------- 行没对齐 -------- */
|
||||
class DoesNotSync <: ParseExcept {
|
||||
DoesNotSync(count: Int64) {
|
||||
super("Matrix Line Does Not Sync", count) }
|
||||
}
|
||||
|
||||
/* -------- 未知异常 -------- */
|
||||
class UnknowExcept <: ParseExcept {
|
||||
UnknowExcept(count: Int64) {
|
||||
super("I don't understand what happened", count) }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* i2c - 用户界面
|
||||
*
|
||||
* 这里已经提供了I2C通用接口,可以将上层代码移动到仓颉下面
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <fcntl.h>
|
||||
#ifdef __linux__
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/i2c-dev.h> /* 其中宏的取值可能具有平台特异性 */
|
||||
#include <i2c/smbus.h>
|
||||
#endif
|
||||
|
||||
#include "bot4cj.h" /* 有没有可能在仓颉代码中解析C头文件取得宏 */
|
||||
|
||||
|
||||
/*
|
||||
* 初始化i2c设备
|
||||
*
|
||||
* 动态指定设备文件
|
||||
*
|
||||
* 返回打开的文件句柄或者错误码
|
||||
*/
|
||||
#ifdef __linux__
|
||||
int16_t I2C_init(const char* filepath) {
|
||||
int16_t fd;
|
||||
if ((fd = open(filepath, O_RDWR)) < 0) {
|
||||
perror("Failed to open I2C bus");
|
||||
return BOT4CJ_I2C_OPENED_FAILED;
|
||||
}
|
||||
else {
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
int16_t I2C_init(const char* filepath) {
|
||||
return BOT4CJ_I2C_OPENED_FAILED;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 释放I2C设备文件 */
|
||||
#ifdef __linux__
|
||||
void I2C_free(int16_t fd) {
|
||||
close(fd);
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
void I2C_free(int16_t fd) {
|
||||
/* Windows平台下无需操作 */
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/* 设置I2C地址 */
|
||||
#ifdef __linux__
|
||||
int I2C_setAddr(int16_t fd, uint8_t addr) {
|
||||
if (ioctl(fd, I2C_SLAVE, addr) < 0) {
|
||||
perror("Failed to set I2C address");
|
||||
return BOT4CJ_I2C_ERR;
|
||||
}
|
||||
else {
|
||||
return BOT4CJ_I2C_OK;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
int I2C_setAddr(int16_t fd, uint8_t addr) {
|
||||
return BOT4CJ_I2C_ERR;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 写寄存器 */
|
||||
#ifdef __linux__
|
||||
int I2C_writeReg(int16_t fd, uint8_t reg, uint8_t val) {
|
||||
/*uint8_t buf[] = {reg, val};
|
||||
if (write(fd, buf, 2) != 2) {
|
||||
perror("I2C write error");
|
||||
return BOT4CJ_I2C_ERR;
|
||||
}
|
||||
else {
|
||||
return BOT4CJ_I2C_OK;
|
||||
}*/
|
||||
|
||||
return i2c_smbus_write_byte_data(fd, reg, val);
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
int I2C_writeReg(int16_t fd, uint8_t reg, uint8_t val) {
|
||||
return BOT4CJ_I2C_ERR;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 读寄存器 失败返回0 */
|
||||
#ifdef __linux__
|
||||
uint8_t I2C_readReg(int16_t fd, uint8_t reg) {
|
||||
|
||||
uint8_t value;
|
||||
|
||||
/*if (write(fd, ®, 1) != 1) {
|
||||
perror("I2C read error");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (read(fd, &value, 1)!= 1) {
|
||||
perror("I2C read error");
|
||||
return 0;
|
||||
}*/
|
||||
|
||||
//return value;
|
||||
|
||||
int8_t a = i2c_smbus_read_byte_data(fd, reg);
|
||||
return *(uint8_t *)& a;
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
uint8_t I2C_readReg(int16_t fd, uint8_t reg) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 读寄存器 - 有符号
|
||||
* 失败返回0 */
|
||||
#ifdef __linux__
|
||||
int8_t I2C_readRegFlag(int16_t fd, uint8_t reg) {
|
||||
|
||||
int8_t value;
|
||||
|
||||
//if (write(fd, ®, 1) != 1) {
|
||||
//perror("I2C read error");
|
||||
//return 0;
|
||||
//}
|
||||
|
||||
//if (read(fd, &value, 1)!= 1) {
|
||||
//perror("I2C read error");
|
||||
//return 0;
|
||||
//}
|
||||
|
||||
//return value;
|
||||
|
||||
return i2c_smbus_read_byte_data(fd, reg);
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
int8_t I2C_readRegFlag(int16_t fd, uint8_t reg) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,516 @@
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include "Matrix.hh"
|
||||
|
||||
namespace qing {
|
||||
|
||||
template <typename T>
|
||||
Matrx<T>::Matrx(): row(0), col(0) {}
|
||||
|
||||
template <typename T>
|
||||
Matrx<T>::Matrx(const uint64_t row, const uint64_t col)
|
||||
: row(row), col(col) { /* 该构造函数用于从头开始构造 */
|
||||
arr = std::vector<T>(col * row);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T>::Matrx(const uint64_t row, const uint64_t col, const std::vector<T>& src)
|
||||
: row(row), col(col) { /* 该构造函数用于通过已有的vector进行构造 */
|
||||
arr = src;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T& Matrx<T>::get(uint64_t row, uint64_t col) const { /* 访问器 - 访问矩阵元素 */
|
||||
return arr[col * this->row + row];
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T& Matrx<T>::witch(uint64_t row, uint64_t col) { /* 访问器 - 访问矩阵元素 */
|
||||
return arr[col * this->row + row];
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Matrx<T>::set(uint64_t row, uint64_t col, T v) { /* 设置矩阵元素 */
|
||||
arr[col * this->row + row] = v;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const std::vector<T>& Matrx<T>::get_data() const{ /* 访问器 - 用于获取原始数据 */
|
||||
return arr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const uint64_t Matrx<T>::get_col() const { /* 获取列数 */
|
||||
return col;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const uint64_t Matrx<T>::get_row() const { /* 获取行数 */
|
||||
return row;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool Matrx<T>::has_same_shape(const Matrx<T>& other) { /* 两个矩阵是否具有相同形状 */
|
||||
return other.get_col() == this->get_col() && other.get_row() == this->get_row();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool Matrx<T>::has_dot_shape(const Matrx<T>& other) { /* 具有矩阵乘法的形状 */
|
||||
return this->get_col() == other.get_row();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Matrx<T>::assert_same_shape(const Matrx<T>& other) { /* 断言两个矩阵具有相同形状 */
|
||||
if (!has_same_shape(other))
|
||||
throw std::invalid_argument("The shapes of matrix are not same.");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Matrx<T>::assert_dot_shape(const Matrx<T>& other) { /* 断言两个矩阵具有点积前置形状 */
|
||||
if (!has_dot_shape(other)) {
|
||||
throw std::invalid_argument("The shapes of matrix can not be dot.");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator+(const Matrx<T>& other) { /* 加法操作符重定义 - 矩阵加法 */
|
||||
//assert_same_shape(other); /* 检查两个矩阵形状是否一致 */
|
||||
std::vector<T> v0 = this->get_data();
|
||||
std::vector<T> vec = other.get_data();
|
||||
for (auto it = v0.begin(), it_or = vec.begin(); it != v0.end(); ++it, ++it_or )
|
||||
*it += *it_or;
|
||||
return Matrx<T>{this->get_row(), this->get_col(), v0};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator+(T& item) { /* 矩阵加法广播 */
|
||||
auto len = this->get_data().size();
|
||||
auto v0 = std::vector<T> (len, item);
|
||||
auto matrix = (*this) + Matrx(get_row(), get_col(), v0);
|
||||
|
||||
//auto matrix = *this;
|
||||
//for (auto it = arr.begin(); it != arr.end(); ++it)
|
||||
// *it += item;
|
||||
return matrix;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator-(const Matrx<T>& other) { /* 操作符重载 - 矩阵减法 */
|
||||
//assert_same_shape(other); /* 检查两个矩阵形状是否一致 */
|
||||
std::vector<T> v0 = this->get_data();
|
||||
std::vector <T> vec = other.get_data();
|
||||
for (auto it = v0.begin(), it_or = vec.begin(); it != v0.end(); ++it, ++it_or )
|
||||
*it -= *it_or;
|
||||
return Matrx{this->get_row(), this->get_col(), v0};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator-(T& item) { /* 操作符重载 - 矩阵减法广播 */
|
||||
auto len = this->get_data().size();
|
||||
auto v0 = std::vector<T> (len, item);
|
||||
auto matrix = (*this) - Matrx(get_row(), get_col(), v0);
|
||||
|
||||
//Matrx<T> matrix = *this;
|
||||
//for (auto it = arr.begin(); it != arr.end(); ++it)
|
||||
// *it -= item;
|
||||
return matrix;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::dot(const Matrx<T>& other) { /* 操作符重载 - 矩阵乘法 */
|
||||
//assert_dot_shape(other); /* 它们的形状是否能够点积 */
|
||||
auto matrix = Matrx<T>(this->get_row(), other.get_col());
|
||||
for (uint64_t i=0; i<this->get_row(); ++i) /* 点积运算 */
|
||||
for (uint64_t j=0; j<other.get_col(); ++j) {
|
||||
T sum{0};
|
||||
for (uint64_t k=0; k<this->get_col(); ++k)
|
||||
sum += this->get(i, k) * other.get(k, j);
|
||||
matrix.witch(i, j) = sum;
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator*(T& item) { /* 操作符重载 - 矩阵乘法广播 */
|
||||
std::vector<T> vec = this->get_data();
|
||||
for (auto it = vec.begin(); it != vec.end(); ++it)
|
||||
*it *= item;
|
||||
return Matrx<T>{this->get_row(), this->get_col(), vec};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator*(const Matrx<T>& other) { /* 加法操作符重定义 - 矩阵加法 */
|
||||
//assert_same_shape(other); /* 检查两个矩阵形状是否一致 */
|
||||
std::vector<T> v0 = this->get_data();
|
||||
std::vector<T> vec = other.get_data();
|
||||
for (auto it = v0.begin(), it_or = vec.begin(); it != v0.end(); ++it, ++it_or )
|
||||
*it *= *it_or;
|
||||
return Matrx<T>{this->get_row(), this->get_col(), v0};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator/(T& item) { /* 操作符重载 - 矩阵除法广播 */
|
||||
std::vector<T> vec = this->get_data();
|
||||
for (auto it = vec.begin(); it != vec.end(); ++it)
|
||||
*it /= item;
|
||||
return Matrx<T>(this->get_row(), this->get_col(), vec);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator/(const Matrx<T>& other) { /* 加法操作符重定义 - 矩阵加法 */
|
||||
//assert_same_shape(other); /* 检查两个矩阵形状是否一致 */
|
||||
std::vector<T> v0 = this->get_data();
|
||||
std::vector<T> vec = other.get_data();
|
||||
for (auto it = v0.begin(), it_or = vec.begin(); it != v0.end(); ++it, ++it_or )
|
||||
*it /= *it_or;
|
||||
return Matrx<T>{this->get_row(), this->get_col(), v0};
|
||||
}
|
||||
|
||||
template class Matrx<float>; /* 显式实例化 */
|
||||
template class Matrx<double>;
|
||||
|
||||
}
|
||||
|
||||
/* FIXME: 形状不合法时返回空指针 */
|
||||
using namespace qing;
|
||||
extern "C" {
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* createMatrix(uint64_t row, uint64_t col) {
|
||||
return (void *) new Matrx<float>(row, col);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void freeMatrix(void* M) {
|
||||
delete (Matrx<float>*) M;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
float* witchMatrix(void *m, uint64_t row, uint64_t col) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
return &(M->witch(row, col));
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
float getMatrix(void *m, uint64_t row, uint64_t col) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
return M->get(row, col);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void setMatrix(void *m, uint64_t row, uint64_t col, float v) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
M->set(row, col, v);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
uint64_t getrowMatrix(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
return M->get_row();
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
uint64_t getcolMatrix(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
return M->get_col();
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
bool sameShapeMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
return M->has_same_shape(*S);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
bool dotShapeMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
return M->has_dot_shape(*S);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* plusMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
auto a = new Matrx<float>((*M) + (*S));
|
||||
return (void *) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* plusBroadcastMatrix(void *m, float t) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
auto a = new Matrx<float> ((*M) + t);
|
||||
return (void*) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* subMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
auto a = new Matrx<float>((*M) - (*S));
|
||||
return (void *) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void * subBroadcastMatrix(void *m, float t) {
|
||||
Matrx<float> *M = (Matrx<float> *) m;
|
||||
auto a = new Matrx<float> ((*M) - t);
|
||||
return (void *) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* multiMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
auto a = new Matrx<float>((*M) * (*S));
|
||||
return (void *) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* multiBroadcastMatrix(void *m, float t) {
|
||||
Matrx<float> *M = (Matrx<float> *) m;
|
||||
auto a = new Matrx<float>((*M) * t);
|
||||
return (void*) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* divMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
auto a = new Matrx<float>((*M) / (*S));
|
||||
return (void *) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* divBroadcastMatrix(void *m, float t) {
|
||||
Matrx<float> *M = (Matrx<float> *) m;
|
||||
auto a = new Matrx<float>((*M) / t);
|
||||
return (void*) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* dotMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
auto a = new Matrx<float>(M->dot(*S));
|
||||
return (void *) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* transposeMatrix(void *m) {
|
||||
Matrx<float> *M = (Matrx<float> *)m;
|
||||
auto N= new Matrx<float>(M->get_col(), M->get_row());
|
||||
for (uint64_t row = 0; row< N->get_row(); row++) {
|
||||
for (uint64_t col = 0; col < N->get_col(); col++) {
|
||||
N->witch(row, col) = M->get(col, row);
|
||||
}
|
||||
}
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
/* 正态分布Xavier初始化 */
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* xavierNormalInit(uint64_t fan_in, uint64_t fan_out)
|
||||
{
|
||||
/* 构建空数组 */
|
||||
std::vector<float> weights(fan_in * fan_out);
|
||||
|
||||
/* 高质量的随机数生成器 */
|
||||
static std::mt19937 generator(std::random_device{}());
|
||||
|
||||
/* 计算标准差 */
|
||||
float sigma = std::sqrt(2.0f / (fan_in + fan_out));
|
||||
|
||||
/* 创建正态分布对象,均值为0,标准差为sigma */
|
||||
std::normal_distribution<float> distribution(0.0f, sigma);
|
||||
|
||||
/* 遍历权重矩阵,进行赋值 */
|
||||
uint64_t k=0;
|
||||
for (uint64_t i=0; i<fan_in; ++i)
|
||||
for (uint64_t j=0; j<fan_out; ++j)
|
||||
weights[k++] = distribution(generator);
|
||||
|
||||
/* 构建矩阵 */
|
||||
auto matrix = new Matrx<float>(fan_in, fan_out, weights);
|
||||
return (void *)matrix;
|
||||
}
|
||||
|
||||
/* 均匀分布Xavier初始化函数 */
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* xavierUniformInit(uint64_t fan_in, uint64_t fan_out)
|
||||
{
|
||||
/* 构建空的数组 */
|
||||
auto weights = std::vector<float>(fan_in * fan_out);
|
||||
|
||||
/* 使用高质量的随机数生成器 */
|
||||
static std::mt19937 generator(std::random_device{}());
|
||||
|
||||
/* 计算区间范围:sqrt(2 / (fan_in + fan_out)) */
|
||||
float scale = std::sqrt(2.0f / (fan_in + fan_out));
|
||||
|
||||
/* 创建均匀分布对象,范围为[-scale, scale] */
|
||||
std::uniform_real_distribution<float> distribution(-scale, scale);
|
||||
|
||||
/* 遍历权重矩阵,进行赋值 */
|
||||
uint64_t k=0;
|
||||
for (uint64_t i=0; i<fan_in; ++i)
|
||||
for (uint64_t j=0; j<fan_out; ++j)
|
||||
weights[k++] = distribution(generator);
|
||||
|
||||
/* 构建矩阵对象 */
|
||||
auto matrix = new Matrx<float> (fan_in, fan_out, weights);
|
||||
return (void *)matrix;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* Sigmoid_do(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
x = 1.0 / (1.0 + std::exp(-x));
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* Sigmoid_dot(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& o: v) {
|
||||
o = o * (1.0 - o);
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* ReLU_do(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
x = x > 0 ? x : 0;
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* ReLU_dot(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
x = x > 0 ? 1.0f : 0.0f;
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* Leaky_ReLU_do(void *m, float alpha) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
x = x > 0 ? x : alpha * x;
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* Leaky_ReLU_dot(void *m, float alpha) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
x = x > 0 ? 1.0 : alpha;
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* Tanh_do(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
x = std::tanh(x);
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* Tanh_dot(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
if (x > 20.0) x = 0.0; // tanh(x) ≈ 1, 导数≈0
|
||||
else if (x < -20.0) x = 0.0; // tanh(x) ≈ -1, 导数≈0
|
||||
else {
|
||||
float t = tanh(x);
|
||||
x = 1.0 - t * t;
|
||||
}
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
|
||||
___col___
|
||||
/ \
|
||||
+---------+ \
|
||||
| + + + + | | row - 第一维度
|
||||
| + + + + | |
|
||||
+---------+ /
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <cstdint>
|
||||
namespace qing {
|
||||
template <class T>
|
||||
class Matrx {
|
||||
public:
|
||||
Matrx();
|
||||
Matrx(const uint64_t row, const uint64_t col);
|
||||
Matrx(const uint64_t row, const uint64_t col, const std::vector<T>& src);
|
||||
const T& get(uint64_t row, uint64_t col) const;
|
||||
//-------------旧接口----------------
|
||||
T& witch(uint64_t row, uint64_t col);
|
||||
//-------------新接口----------------
|
||||
void set(uint64_t row, uint64_t col, T v);
|
||||
//-----------------------------------
|
||||
const std::vector<T>& get_data() const;
|
||||
const uint64_t get_col() const;
|
||||
const uint64_t get_row() const;
|
||||
bool has_same_shape(const Matrx<T>& other);
|
||||
bool has_dot_shape(const Matrx<T>& other);
|
||||
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);
|
||||
Matrx<T> operator*(const Matrx<T>& other);
|
||||
Matrx<T> operator*(T& item);
|
||||
Matrx<T> operator/(T& item);
|
||||
Matrx<T> operator/(const Matrx<T>& other);
|
||||
Matrx<T> dot(const Matrx<T>& other);
|
||||
|
||||
void save(std::ostream& out) {
|
||||
out << row << " " << col;
|
||||
for (const auto& item : arr) {
|
||||
out << " ";
|
||||
out << item;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t row, col;
|
||||
std::vector<T> arr;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* I2C操作库 - PCA9685 - MPU6050 */
|
||||
#ifndef QING_I2C
|
||||
#define QING_I2C
|
||||
/* I2C */
|
||||
#define BOT4CJ_I2C_OK 0
|
||||
#define BOT4CJ_I2C_ERR 1
|
||||
#define BOT4CJ_I2C_OPENED_FAILED -1
|
||||
|
||||
#ifdef _WIN64
|
||||
#define DLLEXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define DLLEXPORT
|
||||
#endif
|
||||
|
||||
DLLEXPORT int16_t I2C_init(const char* filepath);
|
||||
DLLEXPORT void I2C_free(int16_t fd);
|
||||
DLLEXPORT int I2C_setAddr(int16_t fd, uint8_t addr);
|
||||
DLLEXPORT int I2C_writeReg(int16_t fd, uint8_t reg, uint8_t val);
|
||||
DLLEXPORT uint8_t I2C_readReg(int16_t fd, uint8_t reg);
|
||||
DLLEXPORT int8_t I2C_readRegFlag(int16_t fd, uint8_t reg);
|
||||
#endif
|
||||
@@ -0,0 +1,80 @@
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include "fft.h"
|
||||
#include "Matrix.hh"
|
||||
using namespace qing;
|
||||
|
||||
|
||||
|
||||
void functf1d(float A[DN])
|
||||
{
|
||||
unsigned long int i;
|
||||
float dt, tt;
|
||||
float p = 0.1;
|
||||
float freq = 10.0; // 10Hz
|
||||
float fs = DN; // 采样率65536Hz
|
||||
|
||||
dt = 1.0 / fs;
|
||||
|
||||
for(i = 0; i < DN; i++)
|
||||
{
|
||||
tt = (float)i * dt;
|
||||
A[i] = sin(2.0 * M_PI * freq * tt) * p; // 正弦波
|
||||
//A[1][i] = 0.0; // 虚部为0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 初始化FFT数组
|
||||
static void fft_init(float A[2][DN], Matrx<float>& M)
|
||||
{
|
||||
for (int i = 0; i < DN; i++) {
|
||||
float a = M.witch(i, 0);
|
||||
A[0][i] = a;
|
||||
A[1][i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算FFT结果
|
||||
static void fft_result(std::vector<float>& R, float A[2][DN])
|
||||
{
|
||||
for (int i = 0; i < DN/2; i++) {
|
||||
float a = pow(A[0][i], 2) + pow(A[1][i], 2);
|
||||
R[i] = sqrt(a) / (DN/2);
|
||||
//std::cout << i << ":\t" << R[i] << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
// 生成正弦波
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* gen_wave()
|
||||
#else
|
||||
void* gen_wave()
|
||||
#endif
|
||||
{
|
||||
std::vector<float> A(DN);
|
||||
functf1d(A.data());
|
||||
return (void *)new Matrx<float>(DN, 1, A);
|
||||
}
|
||||
|
||||
// 开始FFT
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* start_fft(void* M)
|
||||
#else
|
||||
void* start_fft(void* M)
|
||||
#endif
|
||||
{
|
||||
float A[2][DN];
|
||||
std::vector<float> R(N);
|
||||
fft_init(A, *(Matrx<float> *)M);
|
||||
//fft1(A, 1); // 复序列基2 FFT
|
||||
fft1r1(A, 1); // 单实序列
|
||||
fft_result(R, A);
|
||||
return (void*) new Matrx<float>(N/2, 1, R);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef FFT_H
|
||||
#define FFT_H
|
||||
|
||||
#ifndef N
|
||||
#define N 32768
|
||||
#endif
|
||||
|
||||
#ifndef DN
|
||||
#define DN (N*2)
|
||||
#endif
|
||||
|
||||
#define eps 1.0e-9
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void initial(void);
|
||||
void fft1(float A[2][N], int ifft);
|
||||
unsigned long int inverseq(unsigned long int i1,
|
||||
unsigned long int imax);
|
||||
void fft1r2(float A[2][N], float B[2][N], int ifft);
|
||||
void fft1r1(float A[2][DN], int ifft);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,231 @@
|
||||
/* fft1.c: fast Fourier transform base on 2 for one dimensional
|
||||
complex array, direct and inverse,
|
||||
This program is contributed by Jiang Yong, 2003. 1. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "fft.h"
|
||||
static int ifirst=0, sign=-1;
|
||||
static unsigned M, jup[64], kup[64];
|
||||
static float pi, wr[N], wi[N];
|
||||
/********* initial **************/
|
||||
void initial(void)
|
||||
{
|
||||
unsigned long int i, j, ni;
|
||||
float unit, tt;
|
||||
ni = N;
|
||||
M = 0;
|
||||
while (ni > 1) { ni=ni/2; M=M+1; }
|
||||
ni = 1;
|
||||
for(j=0; j<M; j++) ni = ni * 2;
|
||||
if ((ni-N) != 0)
|
||||
{
|
||||
printf("N isn't compatible! (N => %i, ni -> %lu)\n", N, ni);
|
||||
exit(1);
|
||||
}
|
||||
kup[0] = N/2; /* kup[l] = 2 ^ (M - l - 1) */
|
||||
jup[0] =1;
|
||||
for (j=1;j<M;j++)
|
||||
{
|
||||
kup[j]=kup[j-1]/2;
|
||||
jup[j]=jup[j-1]*2;
|
||||
}
|
||||
pi=4.0*atan((float)1.0);
|
||||
unit=2.0*pi/((float)N);
|
||||
tt=0.0;
|
||||
for(i=0;i<N;i++)
|
||||
{
|
||||
wr[i] = cos(tt);
|
||||
wi[i] = sign * sin(tt);
|
||||
tt = tt + unit;
|
||||
}
|
||||
}
|
||||
/** ifft=1<-->FFT ** ifft=-1<-->IFFT **/
|
||||
void fft1(float A[2][N], int ifft)
|
||||
{
|
||||
unsigned long int l, k, i, j, jl0, jl0k, jl1k, reseq, ij;
|
||||
float wrc, wis, ar, ai, x[2][N];
|
||||
if (ifirst == 0) { initial(), ifirst=1; }
|
||||
if (ifft == -1)
|
||||
for(i=0;i<N;i++) A[1][i] = -A[1][i];
|
||||
for (l=0; l<M; l++)
|
||||
{
|
||||
for (j=0; j<jup[l]; j++)
|
||||
{
|
||||
reseq=inverseq(j, l);
|
||||
ij=kup[l]*reseq;
|
||||
jl0=2*kup[l]*j;
|
||||
wrc = wr[ij];
|
||||
wis = wi[ij];
|
||||
for (k=0; k<kup[l]; k++)
|
||||
{
|
||||
jl0k = jl0 + k;
|
||||
jl1k = jl0 + kup[l] + k;
|
||||
ar = A[0][jl1k] * wrc - A[1][jl1k] * wis;
|
||||
ai = A[0][jl1k] * wis + A[1][jl1k] * wrc;
|
||||
A[0][jl1k] = A[0][jl0k] - ar;
|
||||
A[1][jl1k] = A[1][jl0k] - ai;
|
||||
A[0][jl0k] = A[0][jl0k] + ar;
|
||||
A[1][jl0k] = A[1][jl0k] + ai;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=0; i<N; i++)
|
||||
{
|
||||
j=inverseq(i, M);
|
||||
x[0][i] = A[0][j];
|
||||
x[1][i] = A[1][j];
|
||||
}
|
||||
for (i=0; i<N; i++)
|
||||
{
|
||||
A[0][i] = x[0][i];
|
||||
A[1][i] = x[1][i];
|
||||
}
|
||||
if (ifft == -1)
|
||||
for (i=0; i<N; i++)
|
||||
{
|
||||
A[0][i] = A[0][i]/((float)N);
|
||||
A[1][i] = -A[1][i]/((float)N);
|
||||
}
|
||||
}
|
||||
/*********************************/
|
||||
unsigned long int inverseq(unsigned long int i1,
|
||||
unsigned long int imax)
|
||||
{
|
||||
unsigned long int j, ii, i0, inv;
|
||||
i0 = i1;
|
||||
inv = 0;
|
||||
for (j=0; j < imax; j++)
|
||||
{
|
||||
ii=i0%2;
|
||||
inv=2*inv+ii;
|
||||
i0=(i0-ii)/2;
|
||||
}
|
||||
return (inv);
|
||||
}
|
||||
/* ifft=1 <--> FFT ** ifft=-1 <--> IFFT */
|
||||
void fft1r2(float A[2][N], float B[2][N], int ifft)
|
||||
{
|
||||
unsigned long int k, j;
|
||||
float norm, c[2][N];
|
||||
if (ifft != -1)
|
||||
{
|
||||
for (k=0; k<N; k++)
|
||||
{
|
||||
c[0][k]=A[0][k];
|
||||
c[1][k]=B[0][k];
|
||||
}
|
||||
fft1(c, 1);
|
||||
A[0][0] = c[0][0];
|
||||
A[1][0] = 0.0;
|
||||
B[0][0] = c[1][0];
|
||||
B[1][0] = 0.0;
|
||||
for (j=1; j<N; j++)
|
||||
{
|
||||
A[0][j] = 0.5 * (c[0][j] + c[0][N-j]);
|
||||
A[1][j] = 0.5 * (c[1][j] - c[1][N-j]);
|
||||
B[0][j] = 0.5 * (c[1][j] + c[1][N-j]);
|
||||
B[1][j] = -0.5 * (c[0][j] - c[0][N-j]);
|
||||
}
|
||||
} /* end if*/
|
||||
else
|
||||
{
|
||||
for (j=0; j<N/2; j++)
|
||||
/* check {xj}, {yj} is to real {Ak}, {Bk} */
|
||||
{
|
||||
if (j == 0) norm = 2.0 * fabs(A[1][0])
|
||||
+ 2.0 * fabs(B[1][0]);
|
||||
else norm = fabs(A[1][j]+A[1][N-j])
|
||||
+ fabs(A[0][j]-A[0][N-j])
|
||||
+ fabs(B[1][j]+B[1][N-j])
|
||||
+ fabs(B[0][j]-B[0][N-j]);
|
||||
if (norm > eps)
|
||||
{
|
||||
printf("The IDFT of the series isn't real!\n");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
for (j=0; j<N; j++)
|
||||
{
|
||||
c[0][j]=A[0][j] -B[1][j];
|
||||
c[1][j]=A[1][j] +B[0][j];
|
||||
}
|
||||
fft1(c, -1);
|
||||
for (k=0; k<N; k++)
|
||||
{
|
||||
A[0][k] = c[0][k];
|
||||
B[0][k] = c[1][k];
|
||||
A[1][k] = 0.0;
|
||||
B[1][k] = 0.0;
|
||||
}
|
||||
} /* end else */
|
||||
}
|
||||
/* ifft = 1 <--> FFT ** ifft = -1 <--> IFFT */
|
||||
void fft1r1(float A[2][DN], int ifft)
|
||||
{
|
||||
unsigned long int j, k, dk;
|
||||
float norm, c[2][N], d[2][N], tt, hunit, cosj, sinj;
|
||||
hunit = 4.0 * atan(1.0) / (float)N;
|
||||
if ( ifft != -1)
|
||||
{
|
||||
for (k=0; k<N; k++)
|
||||
{
|
||||
dk = k + k;
|
||||
c[0][k] = A[0][dk];
|
||||
d[0][k] = A[0][dk+1];
|
||||
c[1][k] = 0.0;
|
||||
d[1][k] = 0.0;
|
||||
}
|
||||
fft1r2(c, d, 1);
|
||||
for (j = 0; j < N; j++)
|
||||
{
|
||||
tt = (float)j * hunit;
|
||||
cosj = cos(tt);
|
||||
sinj = sign * sin(tt);
|
||||
A[0][j] = c[0][j] + cosj * d[0][j] - sinj * d[1][j];
|
||||
A[1][j] = c[1][j] + cosj * d[1][j] + sinj * d[0][j];
|
||||
}
|
||||
A[0][N] = c[0][0] - d[0][0];
|
||||
A[1][N] = c[1][0] - d[1][0];
|
||||
for(j=1; j<N; j++)
|
||||
{
|
||||
A[0][DN-j] = A[0][j];
|
||||
A[1][DN-j] = -A[1][j];
|
||||
}
|
||||
} // end if
|
||||
|
||||
else
|
||||
{
|
||||
for (j = 0; j <N; j++) // check {xj} is to real {Ak}
|
||||
{
|
||||
if (j ==0) norm = 2.0 * fabs(A[1][0]);
|
||||
else norm = fabs(A[1][j]+A[1][DN-j])+
|
||||
fabs(A[0][j]-A[0][DN-j]);
|
||||
if (norm > eps)
|
||||
{
|
||||
printf("The IDFT of the series {xj} isn't real!\n");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
for (j = 0; j <N; j++)
|
||||
{
|
||||
c[0][j]= 0.5 * (A[0][j]+A[0][N+j]);
|
||||
c[1][j]= 0.5 * (A[1][j]+A[1][N+j]);
|
||||
tt = (float) j * hunit;
|
||||
cosj = cos(tt);
|
||||
sinj = -sign*sin(tt);
|
||||
d[0][j]= 0.5 * (cosj * (A[0][j]-A[0][N+j])
|
||||
-sinj*(A[1][j] - A[1][N+j]));
|
||||
}
|
||||
fft1r2(c, d, -1);
|
||||
for (k=0; k<N; k++)
|
||||
{
|
||||
dk = k+k;
|
||||
A[0][dk]=c[0][k];
|
||||
A[0][dk+1]=d[0][k];
|
||||
A[1][dk]=0.0;
|
||||
A[1][dk+1]=0.0;
|
||||
}
|
||||
} // end else
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 一维卡尔曼滤波器
|
||||
* 使用时先定义一个kalman指针,然后调用kalmanCreate()创建一个滤波器
|
||||
* 每次读取到传感器数据后即可调用KalmanFilter()来对数据进行滤波
|
||||
* 使用示例
|
||||
* extKalman_t p; //定义一个卡尔曼滤波器结构体
|
||||
* float SersorData; //需要进行滤波的数据
|
||||
* KalmanCreate(&p,20,200); //初始化该滤波器的Q=20 R=200参数
|
||||
* while(1)
|
||||
* {
|
||||
* SersorData = sersor(); //获取数据
|
||||
* SersorData = KalmanFilter(&p,SersorData); //对数据进行滤波
|
||||
* }
|
||||
*/
|
||||
|
||||
#include "kalman.h"
|
||||
|
||||
/**
|
||||
* @name kalmanCreate
|
||||
* @brief 创建一个卡尔曼滤波器
|
||||
* @param p: 滤波器
|
||||
* T_Q:系统噪声协方差
|
||||
* T_R:测量噪声协方差
|
||||
*
|
||||
* @retval none
|
||||
* @attention R固定,Q越大,代表越信任侧量值,Q无穷代表只用测量值
|
||||
* 反之,Q越小代表越信任模型预测值,Q为零则是只用模型预测
|
||||
*/
|
||||
void KalmanCreate(extKalman_t* p, float T_Q, float T_R)
|
||||
{
|
||||
p->X_last = (float)0;
|
||||
p->P_last = 0;
|
||||
p->Q = T_Q;
|
||||
p->R = T_R;
|
||||
p->A = 1;
|
||||
p->B = 0;
|
||||
p->H = 1;
|
||||
p->X_mid = p->X_last;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name KalmanFilter
|
||||
* @brief 卡尔曼滤波器
|
||||
* @param p: 滤波器
|
||||
* dat:待滤波数据
|
||||
* @retval 滤波后的数据
|
||||
* @attention Z(k)是系统输入,即测量值 X(k|k)是卡尔曼滤波后的值,即最终输出
|
||||
* A=1 B=0 H=1 I=1 W(K) V(k)是高斯白噪声,叠加在测量值上了,可以不用管
|
||||
* 以下是卡尔曼的5个核心公式
|
||||
* 一阶H'即为它本身,否则为转置矩阵
|
||||
*/
|
||||
|
||||
float KalmanFilter(extKalman_t* p, float dat)
|
||||
{
|
||||
p->X_mid = p->A * p->X_last; //百度对应公式(1) x(k|k-1) = A*X(k-1|k-1)+B*U(k)+W(K)
|
||||
p->P_mid = p->A * p->P_last + p->Q; //百度对应公式(2) p(k|k-1) = A*p(k-1|k-1)*A'+Q
|
||||
p->kg = p->P_mid / (p->P_mid + p->R); //百度对应公式(4) kg(k) = p(k|k-1)*H'/(H*p(k|k-1)*H'+R)
|
||||
p->X_now = p->X_mid + p->kg * (dat - p->X_mid); //百度对应公式(3) x(k|k) = X(k|k-1)+kg(k)*(Z(k)-H*X(k|k-1))
|
||||
p->P_now = (1 - p->kg) * p->P_mid; //百度对应公式(5) p(k|k) = (I-kg(k)*H)*P(k|k-1)
|
||||
p->P_last = p->P_now; //状态更新
|
||||
p->X_last = p->X_now;
|
||||
return p->X_now; //输出预测结果x(k|k)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
#ifndef _KALMAN_H
|
||||
#define _KALMAN_H
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float X_last; //上一时刻的最优结果 X(k-|k-1)
|
||||
float X_mid; //当前时刻的预测结果 X(k|k-1)
|
||||
float X_now; //当前时刻的最优结果 X(k|k)
|
||||
float P_mid; //当前时刻预测结果的协方差 P(k|k-1)
|
||||
float P_now; //当前时刻最优结果的协方差 P(k|k)
|
||||
float P_last; //上一时刻最优结果的协方差 P(k-1|k-1)
|
||||
float kg; //kalman增益
|
||||
float A; //系统参数
|
||||
float B;
|
||||
float Q;
|
||||
float R;
|
||||
float H;
|
||||
} extKalman_t;
|
||||
|
||||
void KalmanCreate(extKalman_t* p, float T_Q, float T_R);
|
||||
float KalmanFilter(extKalman_t* p, float dat);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,354 @@
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#pragma comment(lib, "winmm.lib")
|
||||
#pragma comment(lib, "user32.lib")
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath>
|
||||
#else
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <cmath>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#ifndef M_LN2
|
||||
#define M_LN2 0.693147180559945309417
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows 回调结构
|
||||
struct PlaybackContext {
|
||||
volatile bool done;
|
||||
WAVEHDR* header;
|
||||
};
|
||||
|
||||
// Windows 播放完成回调函数
|
||||
void CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
|
||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
if (uMsg == WOM_DONE) {
|
||||
PlaybackContext* ctx = reinterpret_cast<PlaybackContext*>(dwInstance);
|
||||
ctx->done = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
|
||||
#ifdef _WIN32
|
||||
#define SND_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define SND_EXPORT
|
||||
#endif
|
||||
|
||||
SND_EXPORT bool play_frequency(float frequency, int32_t duration_ms,
|
||||
double volume, int32_t channels,
|
||||
int32_t sample_rate) {
|
||||
// 参数验证
|
||||
if (frequency <= 0.0f || duration_ms <= 0 ||
|
||||
volume <= 0.0 || volume > 1.0 ||
|
||||
channels <= 0 || channels > 8 ||
|
||||
sample_rate <= 0) {
|
||||
std::cerr << "Invalid parameters" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 精确计算帧数:使用 round 而非截断
|
||||
// duration_ms / 1000.0 得到秒数,乘以采样率得到帧数
|
||||
int64_t num_frames = static_cast<int64_t>(
|
||||
std::round(static_cast<double>(sample_rate) * duration_ms / 1000.0)
|
||||
);
|
||||
|
||||
if (num_frames <= 0) {
|
||||
std::cerr << "Duration too short to generate any frames" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 计算实际播放时长(用于调试)
|
||||
double actual_duration_ms = static_cast<double>(num_frames) / sample_rate * 1000.0;
|
||||
std::cout << "Requested duration: " << duration_ms << " ms, "
|
||||
<< "Actual duration: " << actual_duration_ms << " ms, "
|
||||
<< "Frames: " << num_frames << std::endl;
|
||||
|
||||
// 计算总采样点数
|
||||
int32_t total_samples = static_cast<int32_t>(num_frames * channels);
|
||||
std::vector<int16_t> buffer(total_samples);
|
||||
|
||||
// 计算振幅(应用对数音量缩放以获得更自然的音量控制)
|
||||
int32_t amplitude = static_cast<int32_t>(32767.0 * volume);
|
||||
|
||||
// 生成正弦波数据
|
||||
for (int64_t i = 0; i < num_frames; i++) {
|
||||
// 使用 double 计算以提高精度
|
||||
double t = static_cast<double>(i) / sample_rate;
|
||||
double value = sin(2.0 * M_PI * frequency * t);
|
||||
|
||||
// 应用淡入淡出以避免咔嗒声
|
||||
int64_t fade_samples = static_cast<int64_t>(sample_rate * 0.005); // 5ms 淡入淡出
|
||||
double fade_factor = 1.0;
|
||||
|
||||
if (i < fade_samples) {
|
||||
// 淡入:从 0 到 1
|
||||
fade_factor = static_cast<double>(i) / fade_samples;
|
||||
} else if (i >= num_frames - fade_samples) {
|
||||
// 淡出:从 1 到 0
|
||||
fade_factor = static_cast<double>(num_frames - 1 - i) / fade_samples;
|
||||
}
|
||||
|
||||
int16_t sample = static_cast<int16_t>(
|
||||
amplitude * value * fade_factor
|
||||
);
|
||||
|
||||
// 填充所有声道
|
||||
for (int32_t ch = 0; ch < channels; ch++) {
|
||||
buffer[i * channels + ch] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// ===== Windows 实现(带回调,精确计时) =====
|
||||
WAVEFORMATEX wfx;
|
||||
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
||||
wfx.nChannels = channels;
|
||||
wfx.nSamplesPerSec = sample_rate;
|
||||
wfx.wBitsPerSample = 16;
|
||||
wfx.nBlockAlign = channels * 2; // 16-bit = 2 bytes per sample
|
||||
wfx.nAvgBytesPerSec = sample_rate * wfx.nBlockAlign;
|
||||
wfx.cbSize = 0;
|
||||
|
||||
PlaybackContext context;
|
||||
context.done = false;
|
||||
context.header = new WAVEHDR();
|
||||
|
||||
// 打开波形设备,使用回调函数
|
||||
HWAVEOUT hWaveOut;
|
||||
MMRESULT result = waveOutOpen(
|
||||
&hWaveOut,
|
||||
WAVE_MAPPER,
|
||||
&wfx,
|
||||
reinterpret_cast<DWORD_PTR>(waveOutProc), // 回调函数
|
||||
reinterpret_cast<DWORD_PTR>(&context), // 传递给回调的数据
|
||||
CALLBACK_FUNCTION
|
||||
);
|
||||
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
std::cerr << "Cannot open audio device. Error code: " << result << std::endl;
|
||||
delete context.header;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 准备 WAVEHDR
|
||||
WAVEHDR& header = *context.header;
|
||||
ZeroMemory(&header, sizeof(WAVEHDR));
|
||||
header.lpData = reinterpret_cast<char*>(buffer.data());
|
||||
header.dwBufferLength = static_cast<DWORD>(total_samples * sizeof(int16_t));
|
||||
header.dwFlags = 0;
|
||||
header.dwLoops = 0;
|
||||
|
||||
result = waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
std::cerr << "Failed to prepare audio header. Error code: " << result << std::endl;
|
||||
waveOutClose(hWaveOut);
|
||||
delete context.header;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 写入音频数据
|
||||
result = waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
std::cerr << "Failed to write audio data. Error code: " << result << std::endl;
|
||||
waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
|
||||
waveOutClose(hWaveOut);
|
||||
delete context.header;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待播放完成(使用事件等待而非轮询)
|
||||
auto start_time = timeGetTime();
|
||||
while (!context.done) {
|
||||
// 使用 MsgWaitForMultipleObjects 等待消息或超时
|
||||
// 这样可以在等待的同时处理 Windows 消息
|
||||
DWORD wait_result = MsgWaitForMultipleObjects(0, NULL, FALSE, 100, QS_ALLINPUT);
|
||||
|
||||
if (wait_result == WAIT_OBJECT_0) {
|
||||
// 处理 Windows 消息
|
||||
MSG msg;
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
// 超时检查(防止无限等待)
|
||||
DWORD elapsed = timeGetTime() - start_time;
|
||||
if (elapsed > static_cast<DWORD>(duration_ms + 1000)) {
|
||||
std::cerr << "Playback timeout" << std::endl;
|
||||
waveOutReset(hWaveOut);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto end_time = timeGetTime();
|
||||
DWORD actual_play_time = end_time - start_time;
|
||||
std::cout << "Actual playback time: " << actual_play_time << " ms" << std::endl;
|
||||
|
||||
// 清理
|
||||
waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
|
||||
waveOutClose(hWaveOut);
|
||||
delete context.header;
|
||||
|
||||
#else
|
||||
// ===== Linux ALSA 实现 =====
|
||||
snd_pcm_t *pcm_handle = nullptr;
|
||||
const char* device = "default";
|
||||
|
||||
int rc = snd_pcm_open(&pcm_handle, device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot open device '" << device << "': "
|
||||
<< snd_strerror(rc) << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置硬件参数
|
||||
snd_pcm_hw_params_t *hw_params;
|
||||
snd_pcm_hw_params_alloca(&hw_params);
|
||||
|
||||
rc = snd_pcm_hw_params_any(pcm_handle, hw_params);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot get hardware parameters: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = snd_pcm_hw_params_set_access(pcm_handle, hw_params,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set access type: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = snd_pcm_hw_params_set_format(pcm_handle, hw_params,
|
||||
SND_PCM_FORMAT_S16_LE);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set sample format: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = snd_pcm_hw_params_set_channels(pcm_handle, hw_params, channels);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set channel count: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int hw_sample_rate = static_cast<unsigned int>(sample_rate);
|
||||
rc = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params,
|
||||
&hw_sample_rate, 0);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set sample rate: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hw_sample_rate != static_cast<unsigned int>(sample_rate)) {
|
||||
std::cout << "Requested sample rate " << sample_rate
|
||||
<< " Hz not supported, using " << hw_sample_rate << " Hz" << std::endl;
|
||||
}
|
||||
|
||||
// 设置缓冲区大小和周期
|
||||
snd_pcm_uframes_t buffer_size;
|
||||
rc = snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size);
|
||||
if (buffer_size > static_cast<snd_pcm_uframes_t>(num_frames)) {
|
||||
snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hw_params,
|
||||
&reinterpret_cast<snd_pcm_uframes_t&>(num_frames));
|
||||
}
|
||||
|
||||
rc = snd_pcm_hw_params(pcm_handle, hw_params);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set hardware parameters: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置软件参数
|
||||
snd_pcm_sw_params_t *sw_params;
|
||||
snd_pcm_sw_params_alloca(&sw_params);
|
||||
|
||||
rc = snd_pcm_sw_params_current(pcm_handle, sw_params);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot get software parameters: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = snd_pcm_sw_params_set_start_threshold(pcm_handle, sw_params,
|
||||
static_cast<snd_pcm_uframes_t>(num_frames));
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set start threshold: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = snd_pcm_sw_params(pcm_handle, sw_params);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set software parameters: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 播放音频(写入数据)
|
||||
snd_pcm_sframes_t frames_written = 0;
|
||||
snd_pcm_uframes_t frames_to_write = static_cast<snd_pcm_uframes_t>(num_frames);
|
||||
|
||||
// 循环写入直到所有数据都播放完毕
|
||||
while (frames_written < static_cast<snd_pcm_sframes_t>(num_frames)) {
|
||||
snd_pcm_uframes_t remaining = static_cast<snd_pcm_uframes_t>(num_frames - frames_written);
|
||||
|
||||
rc = snd_pcm_writei(pcm_handle,
|
||||
buffer.data() + (frames_written * channels),
|
||||
remaining);
|
||||
|
||||
if (rc == -EPIPE) {
|
||||
// 缓冲区下溢,需要恢复
|
||||
std::cerr << "Buffer underrun occurred" << std::endl;
|
||||
snd_pcm_prepare(pcm_handle);
|
||||
rc = snd_pcm_writei(pcm_handle,
|
||||
buffer.data() + (frames_written * channels),
|
||||
remaining);
|
||||
}
|
||||
|
||||
if (rc < 0) {
|
||||
std::cerr << "Error writing to PCM device: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rc > 0) {
|
||||
frames_written += rc;
|
||||
}
|
||||
}
|
||||
|
||||
// 等待所有数据播放完成
|
||||
rc = snd_pcm_drain(pcm_handle);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Error draining PCM device: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
snd_pcm_close(pcm_handle);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <cuda_runtime.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define CHECK(call) do {\
|
||||
cudaError_t err = call;\
|
||||
if (err != cudaSuccess) {\
|
||||
fprintf(stderr, "CUDA error at %s:%d\n", __FILE__, __LINE__);\
|
||||
fprintf(stderr, "%s\n", cudaGetErrorString(err));\
|
||||
exit(EXIT_FAILURE);\
|
||||
}\
|
||||
} while (0)
|
||||
@@ -0,0 +1,872 @@
|
||||
#include <cstdint>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <random>
|
||||
#include "error.cuh"
|
||||
extern "C" {
|
||||
/* 创建一个设备数组 */
|
||||
static void* _crtDevArr(const size_t m)
|
||||
{
|
||||
void *d;
|
||||
CHECK(cudaMalloc(&d, m));
|
||||
return d;
|
||||
}
|
||||
|
||||
/* 释放一个设备数组 */
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_Free(void* d)
|
||||
#else
|
||||
void Cu4cj_Free(void* d)
|
||||
#endif
|
||||
{
|
||||
CHECK(cudaFree(d));
|
||||
}
|
||||
|
||||
|
||||
/* 创建一个单精度浮点数组 输入数组的长度(不初始化) */
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Cu4cj_Orig(uint64_t n)
|
||||
#else
|
||||
void* Cu4cj_Orig(int64_t n)
|
||||
#endif
|
||||
{
|
||||
return _crtDevArr(n * sizeof(float));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 从主机内存拷贝数据到设备内存
|
||||
* FIXME: 是否需要做成输入目的地址
|
||||
*/
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) float* Cu4cj_ToDev(float* h, uint64_t n)
|
||||
#else
|
||||
float* Cu4cj_ToDev(float* h, int64_t n)
|
||||
#endif
|
||||
{
|
||||
float* d = (float*)Cu4cj_Orig(n);
|
||||
CHECK(cudaMemcpy(d, h, n * sizeof(float), cudaMemcpyHostToDevice));
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
/* 从设备内存拷贝数据到主机内存 */
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_ToHost(float* d, float* h, const uint64_t n)
|
||||
#else
|
||||
void Cu4cj_ToHost(float* d, float* h, int64_t n)
|
||||
#endif
|
||||
{
|
||||
CHECK(cudaMemcpy(h, d, n * sizeof(float), cudaMemcpyDeviceToHost));
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_Copy(float* d, float* h, const uint64_t n)
|
||||
#else
|
||||
void Cu4cj_Copy(float* d, float* h, int64_t n)
|
||||
#endif
|
||||
{
|
||||
CHECK(cudaMemcpy(h, d, n * sizeof(float), cudaMemcpyDeviceToHost));
|
||||
}
|
||||
|
||||
/* 创建一个单精度浮点数组并将其初始化为零
|
||||
* FIXME: 当n == 10000 时进程被杀死 */
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) float* Cu4cj_Zero(const uint64_t n)
|
||||
#else
|
||||
float* Cu4cj_Zero(int64_t n)
|
||||
#endif
|
||||
{
|
||||
float* d = (float*)Cu4cj_Orig(n);
|
||||
CHECK(cudaMemset(d, 0, n*sizeof(float)));
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
/* 正态分布初始化(适用于神经网络权重) */
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) float* Cu4cj_Normal(uint64_t n, uint64_t fan_in, uint64_t fan_out)
|
||||
#else
|
||||
float* Cu4cj_Normal(int64_t n, int64_t fan_in, int64_t fan_out)
|
||||
#endif
|
||||
{
|
||||
// 计算 Xavier/Glorot 初始化的标准差
|
||||
float std = sqrt(2.0f / (fan_in + fan_out));
|
||||
|
||||
// 先在主机上分配内存并生成正态分布随机数
|
||||
float* h = (float*)malloc(n * sizeof(float));
|
||||
|
||||
/* 初始化随机数种子 */
|
||||
srand(time(NULL));
|
||||
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
// 使用 Box-Muller 变换生成正态分布随机数
|
||||
float u1 = (float)rand() / RAND_MAX;
|
||||
float u2 = (float)rand() / RAND_MAX;
|
||||
float z = sqrt(-2.0f * log(u1)) * cos(2.0f * 3.1415926535f * u2);
|
||||
h[i] = z * std;
|
||||
}
|
||||
|
||||
// 在设备上分配内存
|
||||
float* d = (float*)Cu4cj_Orig(n);
|
||||
|
||||
// 将主机内存中的数据拷贝到设备内存
|
||||
CHECK(cudaMemcpy(d, h, n * sizeof(float), cudaMemcpyHostToDevice));
|
||||
|
||||
// 释放主机内存
|
||||
free(h);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 向量加法
|
||||
static void __global__ _devAdd(float* d1, float* d2, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] + d2[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 向量加法
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_Add(float* d1, float* d2, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_Add(float* d1, float* d2, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devAdd<<<(n + 255) / 256, 256>>>(d1, d2, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 向量减法
|
||||
static void __global__ _devSub(float* d1, float* d2, float* r, int64_t n)
|
||||
{
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] - d2[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 向量减法
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_Sub(float* d1, float* d2, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_Sub(float* d1, float* d2, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devSub<<<(n + 255) / 256, 256>>>(d1, d2, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 向量依次相乘
|
||||
static void __global__ _devMul(float* d1, float* d2, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] * d2[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 向量依次相乘
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_Mul(float* d1, float* d2, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_Mul(float* d1, float* d2, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devMul<<<(n + 255) / 256, 256>>>(d1, d2, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 向量依次相除
|
||||
static void __global__ _devDiv(float* d1, float* d2, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] / d2[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 向量依次相除
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_Div(float* d1, float* d2, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_Div(float* d1, float* d2, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devDiv<<<(n + 255) / 256, 256>>>(d1, d2, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 向量加法广播
|
||||
static void __global__ _devAddBroadcast(float* d1, float a, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] + a;
|
||||
}
|
||||
}
|
||||
|
||||
// 加法广播
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_AddBroadcast(float* d1, float a, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_AddBroadcast(float* d1, float a, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devAddBroadcast<<<(n + 255) / 256, 255>>>(d1, a, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 减法广播
|
||||
static void __global__ _devSubBroadcast(float* d1, float a, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] - a;
|
||||
}
|
||||
}
|
||||
|
||||
// 减法广播
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_SubBroadcast(float* d1, float a, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_SubBroadcast(float* d1, float a, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devSubBroadcast<<<(n + 255) / 256, 256>>>(d1, a, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 乘法广播
|
||||
static void __global__ _devMulBroadcast(float* d1, float a, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] * a;
|
||||
}
|
||||
}
|
||||
|
||||
// 乘法广播
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_MulBroadcast(float* d1, float a, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_MulBroadcast(float* d1, float a, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devMulBroadcast<<<(n + 255) / 256, 256>>>(d1, a, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 除法广播
|
||||
static void __global__ _devDivBroadcast(float* d1, float a, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] / a;
|
||||
}
|
||||
}
|
||||
|
||||
// 除法广播
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_DivBroadcast(float* d1, float a, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_DivBroadcast(float* d1, float a, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devDivBroadcast<<<(n + 255) / 256, 256>>>(d1, a, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 矩阵乘法
|
||||
static void __global__ _devMatDot(float* d1, float* d2, float* r, int64_t m, int64_t n, int64_t k) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
int j = blockIdx.y * blockDim.y + threadIdx.y;
|
||||
if (i < m && j < k) {
|
||||
float sum = 0.0f;
|
||||
for (int p = 0; p < n; p++) {
|
||||
sum += d1[i * n + p] * d2[p * k + j];
|
||||
}
|
||||
r[i * k + j] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
// 矩阵乘法
|
||||
// m、n、k 分别为矩阵 A、B、C 的行数、列数
|
||||
// 其中 n 为矩阵 A 的列数,也为矩阵 B 的行数
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_MatDot(float* d1, float* d2, float* d_r, uint64_t m, uint64_t n, uint64_t k)
|
||||
#else
|
||||
void Cu4cj_MatDot(float* d1, float* d2, float* d_r, int64_t m, int64_t n, int64_t k)
|
||||
#endif
|
||||
{
|
||||
dim3 blockDim(16, 16);
|
||||
dim3 gridDim((m + blockDim.x - 1) / blockDim.x, (k + blockDim.y - 1) / blockDim.y);
|
||||
_devMatDot<<<gridDim, blockDim>>>(d1, d2, d_r, m, n, k);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 矩阵转置
|
||||
static void __global__ _devTransport(float* d1, float* d2, int64_t m, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
int j = blockIdx.y * blockDim.y + threadIdx.y;
|
||||
if (i < m && j < n) {
|
||||
d2[j * m + i] = d1[i * n + j];
|
||||
}
|
||||
}
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) float* Cu4cj_Transport(float *m, uint64_t r, uint64_t c)
|
||||
#else
|
||||
float* Cu4cj_Transport(float *m, uint64_t r, uint64_t c)
|
||||
#endif
|
||||
{
|
||||
float *d_r = (float*)Cu4cj_Zero(c * r * sizeof(float));
|
||||
_devTransport<<<1, c * r>>>(m, d_r, c, r);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return d_r;
|
||||
}
|
||||
|
||||
|
||||
// Sigmoid 激活函数,输入为矩阵,输出为矩阵
|
||||
static void __global__ _devSigmoid(float* d1, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = 1.0f / (1.0f + exp(-d1[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// Sigmoid 导数
|
||||
static void __global__ _devSigmoidDerivative(float* d1, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] * (1.0f - d1[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// ReLU
|
||||
static void __global__ _devReLU(float* d1, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] > 0.0f ? d1[i] : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// ReLU 导数
|
||||
static void __global__ _devReLUDerivative(float* d1, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] > 0.0f ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Leaky_ReLU
|
||||
static void __global__ _devLeakyReLU(float* d1, float* r, int64_t n, float a) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] > 0.0f ? d1[i] : a * d1[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Leaky_ReLU 导数
|
||||
static void __global__ _devLeakyReLU_Derivative(float* d1, float* r, int64_t n, float a) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] > 0.0f ? 1.0f : a;
|
||||
}
|
||||
}
|
||||
|
||||
// Tanh 激活函数,输入为矩阵,输出为矩阵
|
||||
static void __global__ _devTanh(float* d1, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = (exp(d1[i]) - exp(-d1[i])) / (exp(d1[i]) + exp(-d1[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// Tanh 导数
|
||||
static void __global__ _devTanhDerivative(float* d1, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = 1.0f - d1[i] * d1[i];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace qing {
|
||||
class Matrix {
|
||||
public:
|
||||
Matrix(uint64_t r, uint64_t c) {
|
||||
this->r = r;
|
||||
this->c = c;
|
||||
this->d = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
}
|
||||
Matrix(uint64_t r, uint64_t c, float* d) {
|
||||
this->r = r;
|
||||
this->c = c;
|
||||
this->d = d;
|
||||
}
|
||||
Matrix(const Matrix& m) {
|
||||
this->r = m.getR();
|
||||
this->c = m.getC();
|
||||
this->d = (float*)Cu4cj_Zero(m.getR() * m.getC() * sizeof(float));
|
||||
Cu4cj_Copy(m.getD(), d, m.getR() * m.getC() * sizeof(float));
|
||||
}
|
||||
~Matrix() {
|
||||
Cu4cj_Free(d);
|
||||
}
|
||||
uint64_t getR() const {
|
||||
return r;
|
||||
}
|
||||
uint64_t getC() const {
|
||||
return c;
|
||||
}
|
||||
float* getD() const {
|
||||
return d;
|
||||
}
|
||||
float get(uint64_t r, uint64_t c) const {
|
||||
float h;
|
||||
CHECK(cudaMemcpy(&h, &d[c * this->r + r], sizeof(float), cudaMemcpyDeviceToHost));
|
||||
return h;
|
||||
}
|
||||
void set(uint64_t r, uint64_t c, float v) {
|
||||
CHECK(cudaMemcpy(&d[c * this->r + r], &v, sizeof(float), cudaMemcpyHostToDevice));
|
||||
}
|
||||
bool hasSameShape(const Matrix& m) {
|
||||
return r == m.getR() && c == m.getC();
|
||||
}
|
||||
bool hasDotShape(const Matrix& m) {
|
||||
return c == m.getR();
|
||||
}
|
||||
Matrix operator+(const Matrix& m) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_Add(d, m.getD(), d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator+(float a) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_AddBroadcast(d, a, d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator-(const Matrix& m) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_Sub(d, m.getD(), d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator-(float a) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_SubBroadcast(d, a, d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator*(const Matrix& m) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_Mul(d, m.getD(), d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator*(float a) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_MulBroadcast(d, a, d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator/(const Matrix& m) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_Div(d, m.getD(), d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator/(float a) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_DivBroadcast(d, a, d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix dot(const Matrix& m) {
|
||||
//if (!hasDotShape(m)) {
|
||||
// throw std::invalid_argument("Dot product: matrices must have compatible shapes");
|
||||
//}
|
||||
float *d_r = (float*)Cu4cj_Zero(r * m.getC() * sizeof(float));
|
||||
Cu4cj_MatDot(d, m.getD(), d_r, r, c, m.getC());
|
||||
return Matrix(r, m.getC(), d_r);
|
||||
}
|
||||
Matrix T() {
|
||||
float *d_r = (float*)Cu4cj_Transport(d, r, c);
|
||||
return Matrix(c, r, d_r);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t r, c;
|
||||
float* d;
|
||||
};
|
||||
}
|
||||
|
||||
using namespace qing;
|
||||
extern "C" {
|
||||
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* createMatrix(const uint64_t r, const uint64_t c)
|
||||
#else
|
||||
void* createMatrix(int64_t r, int64_t c)
|
||||
#endif
|
||||
{
|
||||
return(void*) new Matrix(r, c);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void freeMatrix(void* m)
|
||||
#else
|
||||
void freeMatrix(void* m)
|
||||
#endif
|
||||
{
|
||||
delete (Matrix*)m;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* xavierNormalInit(uint64_t fan_in, uint64_t fan_out)
|
||||
#else
|
||||
void* xavierNormalInit(uint64_t fan_in, uint64_t fan_out)
|
||||
#endif
|
||||
{
|
||||
float* d = Cu4cj_Normal(fan_in * fan_out, fan_in, fan_out);
|
||||
return (void *)new Matrix(fan_in, fan_out, d);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* xavierUniformInit(uint64_t fan_in, uint64_t fan_out)
|
||||
#else
|
||||
void* xavierUniformInit(uint64_t fan_in, uint64_t fan_out)
|
||||
#endif
|
||||
{
|
||||
uint64_t n = fan_in * fan_out;
|
||||
|
||||
// 先在主机上分配内存并生成正态分布随机数
|
||||
float* h = (float*)malloc(n * sizeof(float));
|
||||
|
||||
/* 使用高质量的随机数生成器 */
|
||||
static std::mt19937 generator(std::random_device{}());
|
||||
|
||||
/* 计算区间范围:sqrt(2 / (fan_in + fan_out)) */
|
||||
float scale = std::sqrt(2.0f / (fan_in + fan_out));
|
||||
|
||||
/* 创建均匀分布对象,范围为[-scale, scale] */
|
||||
std::uniform_real_distribution<float> distribution(-scale, scale);
|
||||
|
||||
/* 遍历权重矩阵,进行赋值 */
|
||||
uint64_t k=0;
|
||||
for (uint64_t i=0; i<fan_in; ++i)
|
||||
for (uint64_t j=0; j<fan_out; ++j)
|
||||
h[k++] = distribution(generator);
|
||||
|
||||
|
||||
|
||||
// 在设备上分配内存
|
||||
float* d = (float*)Cu4cj_Orig(n*sizeof(float));
|
||||
|
||||
// 将主机内存中的数据拷贝到设备内存
|
||||
CHECK(cudaMemcpy(d, h, n * sizeof(float), cudaMemcpyHostToDevice));
|
||||
|
||||
// 释放主机内存
|
||||
free(h);
|
||||
|
||||
return (void *)new Matrix(fan_in, fan_out, d);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) uint64_t getrowMatrix(void *m)
|
||||
#else
|
||||
uint64_t getrowMatrix(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
return m1->getR();
|
||||
}
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) uint64_t getcolMatrix(void *m)
|
||||
#else
|
||||
uint64_t getcolMatrix(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
return m1->getC();
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) float getMatrix(void *m, uint64_t r, uint64_t c)
|
||||
#else
|
||||
float getMatrix(void *m, uint64_t r, uint64_t c)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
return m1->get(r, c);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void setMatrix(void *m, uint64_t r, uint64_t c, float v)
|
||||
#else
|
||||
void setMatrix(void *m, uint64_t r, uint64_t c, float v)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
m1->set(r, c, v);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) bool sameShapeMatrix(void *m, void *s)
|
||||
#else
|
||||
bool sameShapeMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
return (*m1).hasSameShape(*s1);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) bool dotShapeMatrix(void *m, void *s)
|
||||
#else
|
||||
bool dotShapeMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
return (*m1).hasDotShape(*s1);
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* plusMatrix(void *m, void *s)
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
void* plusMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
Matrix r = *m1 + *s1;
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* plusBroadcastMatrix(void *m, float a)
|
||||
#else
|
||||
void* plusBroadcastMatrix(void *m, float a)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix r = *m1 + a;
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* subMatrix(void *m, void *s)
|
||||
#else
|
||||
void* subMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
Matrix r = *m1 - *s1;
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* subBroadcastMatrix(void *m, float a)
|
||||
#else
|
||||
void* subBroadcastMatrix(void *m, float a)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix r = *m1 - a;
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* multiMatrix(void *m, void *s)
|
||||
#else
|
||||
void* multiMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
Matrix r = (*m1) * (*s1);
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* multiBroadcastMatrix(void *m, float a)
|
||||
#else
|
||||
void* multiBroadcastMatrix(void *m, float a)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix r = (*m1) * a;
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* divMatrix(void *m, void *s)
|
||||
#else
|
||||
void* divMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
Matrix r = (*m1) / (*s1);
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* divBroadcastMatrix(void *m, float a)
|
||||
#else
|
||||
void* divBroadcastMatrix(void *m, float a)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix r = (*m1) / a;
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* dotMatrix(void *m, void *s)
|
||||
#else
|
||||
void* dotMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
Matrix r = (*m1).dot(*s1);
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* transposeMatrix(void *m)
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
void* transposeMatrix(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix r = m1->T();
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Sigmoid_do(void *m)
|
||||
#else
|
||||
void* Sigmoid_do(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devSigmoid<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Sigmoid_dot(void *m)
|
||||
#else
|
||||
void* Sigmoid_dot(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devSigmoidDerivative<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* ReLU_do(void *m)
|
||||
#else
|
||||
void* ReLU_do(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devReLU<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* ReLU_dot(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devReLUDerivative<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Leaky_ReLU_do(void *m, float a)
|
||||
#else
|
||||
void* LeakyReLU_do(void *m, float a)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devLeakyReLU<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC(), a);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Leaky_ReLU_dot(void *m, float a)
|
||||
#else
|
||||
void* LeakyReLU_dot(void *m, float a)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devLeakyReLU_Derivative<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC(), a);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
// Tanh
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Tanh_do(void *m)
|
||||
#else
|
||||
void* Tanh_do(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devTanh<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Tanh_dot(void *m)
|
||||
#else
|
||||
void* Tanh_dot(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devTanhDerivative<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
|
||||
/* TODO(20260518-qing): 稍后我们专门做一个i2c代码仓库,这样就能够区分不同架构上面的int的大小
|
||||
* TODO(20260615-qing): 有没有可能只做一个简单的封装不引入异常机制 */
|
||||
|
||||
package bot4cj.hd
|
||||
import pthread4cj.*
|
||||
import ctype4cj.*
|
||||
|
||||
/* -------- 我们将直接调用glibc中的一些函数 -------- */
|
||||
foreign func ioctl(fd: C_Int, addr: C_ULong, ...): C_Int /* -------- 成功返回非负整数,失败返回-1 -------- */
|
||||
|
||||
const I2C_FAILED = -1i32 /* -------- I2C关闭文件失败 -------- */
|
||||
const I2C_SUCCESS = 0i32 /* -------- I2C关闭文件成功 -------- */
|
||||
const I2C_SLAVE = 0x0703u64 /* -------- at least on arm64 -------- */
|
||||
|
||||
/* -------- OPEN I2C DEVICE 打开I2C设备 -------- */
|
||||
@C
|
||||
public func openI2cDev(path: CString): Int32 {
|
||||
unsafe {
|
||||
let fd = libc_open(path, O_RDWR)
|
||||
if (fd > 0) {
|
||||
I2cLock_create(fd) /* -------- 为I2C设备创建锁 -------- */
|
||||
return fd
|
||||
}
|
||||
else {
|
||||
throw I2cOpenedFailed("?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 简化版,判断在外部实现 ---- */
|
||||
@C
|
||||
public func openI2cDev1(path: CString): C_Int {
|
||||
unsafe {
|
||||
let fd = libc_open(path, O_RDWR)
|
||||
I2cLock_create(fd)
|
||||
return fd
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- CLOSE I2C DEVICE 关闭I2C设备 -------- */
|
||||
@C
|
||||
public func closeI2cDev(fd: Int32): Unit {
|
||||
unsafe {
|
||||
I2cLock_destroy(fd) /* -------- 销毁I2C设备的锁 -------- */
|
||||
let ret = libc_close(fd)
|
||||
if (ret == I2C_FAILED) {
|
||||
throw I2cClosedFailed("?") }
|
||||
else if (ret == I2C_SUCCESS) { ; }
|
||||
else {
|
||||
throw I2cUnknowExp(
|
||||
"?", "$function returned: {ret.toString()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@C
|
||||
public func closeI2cDev1(fd: C_Int): Unit {
|
||||
unsafe {
|
||||
I2cLock_destroy(fd)
|
||||
libc_close(fd)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- SET I2C ADDR 设置I2C地址 -------- */
|
||||
@C
|
||||
public func setI2cAddr(fd: Int32, addr: UInt8): Unit {
|
||||
unsafe {
|
||||
let ret = ioctl(fd, I2C_SLAVE, addr)
|
||||
if (ret < 0) {
|
||||
throw I2cSetAddressFailed("?") }
|
||||
}
|
||||
}
|
||||
|
||||
@C
|
||||
public func setI2cAddr1(fd: C_Int, addr: UInt8): C_Int {
|
||||
unsafe {
|
||||
return ioctl(fd, I2C_SLAVE, addr)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- WRITE I2C REG 写I2C寄存器 -------- */
|
||||
@C
|
||||
public func writeI2cReg(fd: Int32, reg: UInt8, val:UInt8): Unit {
|
||||
var a: VArray<UInt8, $2> = [reg, val]
|
||||
var p: CPointer<Unit> = unsafe { CPointer<Unit>(inout a) }
|
||||
if (2 != unsafe { libc_write(fd, p, 2) }) {
|
||||
throw I2cWriteRegFailed("?")
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- READ I2C REG 读I2C寄存器 --------
|
||||
* INFO(20260522-qing): 读取寄存器之前要先写寄存器地址
|
||||
* INFO(20260616-qing): 这个函数只读取一个字节的数据
|
||||
* -------- */
|
||||
@C
|
||||
public func readI2cReg(fd: Int32, reg: UInt8): Int8 {
|
||||
var ptr = reg
|
||||
if (1 != unsafe { libc_write(fd, CPointer<Unit>(inout ptr), 1) }) {
|
||||
throw I2cReadRegFailed("?")
|
||||
}
|
||||
var value: Int8 = 0
|
||||
var p = unsafe { CPointer<Unit>(inout value) }
|
||||
let ret = unsafe { libc_read(fd, p, 1) }
|
||||
if (ret != 1) { throw I2cReadRegFailed("?") }
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
/* -------- I2C锁具 --------
|
||||
* INFO(20260521-qing): 因为每个I2C接口在通信时具有原子化的需求
|
||||
* - 需要一个互斥锁集合,在打开第一个I2C设备时被初始化,
|
||||
* - 释放最后一个I2C设备时,将锁具释放
|
||||
* - **每次进行I2C通信时,进行上锁操作** */
|
||||
@C
|
||||
struct I2C_Lock {
|
||||
var i2c_fd: Int32 = 0 /* -------- 这个锁绑定的I2C设备文件句柄 -------- */
|
||||
var lock = CPointer<UInt8>() /* -------- phread_mutex_t 锁对象 -------- */
|
||||
var is_active = false /* -------- 该锁具是否被激活 --------*/
|
||||
}
|
||||
|
||||
|
||||
var MAIN_LOCK = CPointer<UInt8>() /* -------- 主锁,操作锁具时使用 -------- */
|
||||
var LOCKS = CPointer<I2C_Lock>() /* -------- 指向锁具内存的指针 -------- */
|
||||
const LOCKS_SIZE: Int64 = 20 /* -------- 锁具的容量 --------*/
|
||||
|
||||
/* -------- 为一个I2C设备注册锁具 -------- */
|
||||
@C
|
||||
func I2cLock_create(fd: Int32): Unit {
|
||||
unsafe {
|
||||
/* -------- 初始化主要的锁 -------- */
|
||||
if (MAIN_LOCK.isNull()) {
|
||||
MAIN_LOCK = _mutex_create()
|
||||
LOCKS = LibC.malloc<I2C_Lock>(count: LOCKS_SIZE)
|
||||
for (i in 0..20) {
|
||||
let tmp = I2C_Lock()
|
||||
(LOCKS+i).write(tmp)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 获取主锁 --------*/
|
||||
_mutex_lock(MAIN_LOCK)
|
||||
var flag = false
|
||||
|
||||
for (idx in 0..LOCKS_SIZE) {
|
||||
var lock = (LOCKS+idx).read()
|
||||
if (lock.is_active == false) {
|
||||
lock.is_active = true
|
||||
lock.i2c_fd = fd
|
||||
lock.lock = _mutex_create()
|
||||
_mutex_init(lock.lock)
|
||||
(LOCKS+idx).write(lock)
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_mutex_unlock(MAIN_LOCK)
|
||||
if (flag ) { return } /* -------- 出口 -------- */
|
||||
throw OverflowException("LOCKS is over.")
|
||||
}
|
||||
}
|
||||
/* -------- 为一个I2C设备注销锁具 -------- */
|
||||
@C
|
||||
func I2cLock_destroy(fd: Int32): Unit {
|
||||
unsafe {
|
||||
/* -------- 检查锁具有没有被初始化 --------*/
|
||||
if (MAIN_LOCK.isNull()) {
|
||||
throw NoneValueException("no i2c lock")
|
||||
}
|
||||
_mutex_lock(MAIN_LOCK)
|
||||
|
||||
var flag_any = false
|
||||
var flag_target = false
|
||||
for (idx in 0..LOCKS_SIZE) {
|
||||
var lock = (LOCKS+idx).read()
|
||||
if (lock.is_active == true && lock.i2c_fd == fd) {
|
||||
lock.is_active = false
|
||||
_mutex_destroy(lock.lock)
|
||||
_mutex_free(lock.lock)
|
||||
(LOCKS+idx).write(lock)
|
||||
flag_target = true
|
||||
/* -------- NOTE: 扫描整个锁具不终止 -------- */
|
||||
}
|
||||
else if (lock.is_active == true && lock.i2c_fd != fd) {
|
||||
flag_any = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_mutex_unlock(MAIN_LOCK)
|
||||
|
||||
/* -------- 如果锁具里已经没有I2C锁, 就释放整个锁具 -------- */
|
||||
if (!flag_any) {
|
||||
LibC.free(LOCKS)
|
||||
_mutex_free(MAIN_LOCK)
|
||||
MAIN_LOCK = CPointer<UInt8>()
|
||||
}
|
||||
|
||||
/* -------- 出口 -------- */
|
||||
if (flag_target) {
|
||||
return }
|
||||
|
||||
|
||||
throw NoneValueException("i2c lock nor found")
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 为一个I2C锁具加锁 -------- */
|
||||
@C
|
||||
func I2cLock_lock(fd: Int32): Unit {
|
||||
unsafe {
|
||||
if (MAIN_LOCK.isNull()) {
|
||||
throw NoneValueException("no i2c lock")
|
||||
}
|
||||
|
||||
_mutex_lock(MAIN_LOCK)
|
||||
|
||||
var flag = false
|
||||
for (idx in 0..LOCKS_SIZE) {
|
||||
var lock = (LOCKS+idx).read()
|
||||
if (lock.is_active == true && lock.i2c_fd == fd) {
|
||||
_mutex_lock(lock.lock)
|
||||
(LOCKS+idx).write(lock)
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_mutex_unlock(MAIN_LOCK)
|
||||
if (flag) { return }
|
||||
throw NoneValueException("i2c lock nor found")
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 为一个I2C锁具解锁 -------- */
|
||||
@C
|
||||
func I2cLock_unlock(fd: Int32): Unit {
|
||||
unsafe {
|
||||
if (MAIN_LOCK.isNull()) {
|
||||
throw NoneValueException("no i2c lock")
|
||||
}
|
||||
_mutex_lock(MAIN_LOCK)
|
||||
|
||||
var flag = false
|
||||
for (idx in 0..LOCKS_SIZE) {
|
||||
var lock = (LOCKS+idx).read()
|
||||
if (lock.is_active == true && lock.i2c_fd == fd) {
|
||||
_mutex_unlock(lock.lock)
|
||||
(LOCKS+idx).write(lock)
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
_mutex_unlock(MAIN_LOCK)
|
||||
if (flag) {return}
|
||||
throw NoneValueException("i2c lock nor found")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* -------- 原生I2C通信类--------
|
||||
* INFO(20260521-qing): 直接调用glibc的接口 */
|
||||
public class I2C_Orig {
|
||||
private var fd: Int32 = -1
|
||||
private var path: String
|
||||
public init (path: String) {
|
||||
this.path = path
|
||||
let s1 = unsafe {
|
||||
LibC.mallocCString(path) }
|
||||
fd = unsafe { openI2cDev(s1) }
|
||||
}
|
||||
|
||||
public func _close(): Unit {
|
||||
unsafe { closeI2cDev(fd) }
|
||||
}
|
||||
|
||||
public func setAddr(addr: UInt8) {
|
||||
unsafe { setI2cAddr(fd, addr) }
|
||||
}
|
||||
|
||||
public func writeReg(reg: UInt8, val:UInt8): Unit {
|
||||
unsafe { writeI2cReg(fd, reg, val) }
|
||||
}
|
||||
|
||||
public func readReg(reg: UInt8): Int8 {
|
||||
var value = unsafe { readI2cReg(fd, reg) }
|
||||
return value
|
||||
}
|
||||
|
||||
/*--------获取文件句柄--------*/
|
||||
public func getFd(): Int32 {
|
||||
return fd
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* -------- I2C设备异常 -------- */
|
||||
open private class I2cExcept <: Exception {
|
||||
public I2cExcept(dev: String, msg: String) {
|
||||
super('${dev}: ${msg}') }
|
||||
}
|
||||
|
||||
/* -------- 打开I2C设备失败 -------- */
|
||||
private class I2cOpenedFailed <: I2cExcept {
|
||||
public I2cOpenedFailed(dev: String) {
|
||||
super(dev, "failed to open i2c device") }
|
||||
}
|
||||
|
||||
/* -------- 关闭I2C设备失败 -------- */
|
||||
private class I2cClosedFailed <: I2cExcept {
|
||||
public I2cClosedFailed(dev: String) {
|
||||
super(dev, "failed to close i2c device") }
|
||||
}
|
||||
|
||||
/* -------- I2C设置地址失败 -------- */
|
||||
private class I2cSetAddressFailed <: I2cExcept {
|
||||
public I2cSetAddressFailed(dev: String) {
|
||||
super(dev, "failed to open i2c device") }
|
||||
}
|
||||
|
||||
/* -------- I2C写从机I2C失败 -------- */
|
||||
private class I2cWriteRegFailed <: I2cExcept {
|
||||
public I2cWriteRegFailed(dev: String) {
|
||||
super(dev, "failed to write i2c reg") }
|
||||
}
|
||||
|
||||
/* -------- I2C读从机I2C失败 -------- */
|
||||
private class I2cReadRegFailed <: I2cExcept {
|
||||
public I2cReadRegFailed(dev: String) {
|
||||
super(dev, "failed to read i2c reg") }
|
||||
}
|
||||
|
||||
/* -------- 未知I2C异常 -------- */
|
||||
private class I2cUnknowExp <: I2cExcept {
|
||||
public I2cUnknowExp(dev: String, msg: String) {
|
||||
super(dev, msg) }
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/* ----------------
|
||||
* MPU6050陀螺仪与加速度计模块
|
||||
* -
|
||||
* TODO(20260516-qing): 陀螺仪需要卡尔曼滤波、通过四元数等方式获取姿态角
|
||||
* - 所以应该使用一个C/C++线程来持续地采样,并且留一个接口进行获取
|
||||
*/
|
||||
|
||||
package bot4cj.hd
|
||||
import std.time.*
|
||||
import std.math.*
|
||||
import pthread4cj.*
|
||||
import bot4cj.math.*
|
||||
|
||||
const ADDR_0: UInt8 = 0x68 /* 默认地址 */
|
||||
const ADDR_1: UInt8 = 0x68 /* 地址2 */
|
||||
const PWR_MGMT_1: UInt8 = 0x6B /* 电源管理寄存器1 */
|
||||
const PWR_NOSLEEP: UInt8 = 0x00 /* 电源管理 - 非睡眠模式 */
|
||||
const GYRO_CONF_REG: UInt8 = 0x1B /* 陀螺仪配置寄存器 */
|
||||
const GYRO_FS_500DPS: UInt8 = 0x08 /* 陀螺仪量程 - 正负500度/秒 */
|
||||
const GYRO_REG_X_HIGH: UInt8 = 0x43 /* x - 陀螺仪横轴 高字节 */
|
||||
const GYRO_REG_X_LOW: UInt8 = 0x44
|
||||
const GYRO_REG_Y_HIGH: UInt8 = 0x45 /* y */
|
||||
const GYRO_REG_Y_LOW: UInt8 = 0x46
|
||||
const GYRO_REG_Z_HIGH: UInt8 = 0x47 /* z */
|
||||
const GYRO_REG_Z_LOW: UInt8 = 0x48
|
||||
const ACCEL_CONF_REG: UInt8 = 0x1C /* 加速度计配置寄存器 */
|
||||
const ACCEL_FS_8g: UInt8 = 0x10 /* 加速度计量程 正负8g */
|
||||
const ACCEL_REG_X_HIGH: UInt8 = 0x3B
|
||||
const ACCEL_REG_X_LOW: UInt8 = 0x3C
|
||||
const ACCEL_REG_Y_HIGH: UInt8 = 0x3D
|
||||
const ACCEL_REG_Y_LOW: UInt8 = 0x3E
|
||||
const ACCEL_REG_Z_HIGH: UInt8 = 0x3F
|
||||
const ACCEL_REG_Z_LOW: UInt8 = 0x40
|
||||
|
||||
/* -------- MPU6050传感器数据 -------- */
|
||||
@C
|
||||
public struct MPU6050_sensorData {
|
||||
public var accel_x: Int16 = 0 /* 加速度计 */
|
||||
public var accel_y: Int16 = 0
|
||||
public var accel_z: Int16 = 0
|
||||
public var gyro_x: Int16 = 0 /* 陀螺仪 */
|
||||
public var gyro_y: Int16 = 0
|
||||
public var gyro_z: Int16 = 0
|
||||
public func print() { /* 格式化打印 */
|
||||
println("[accel x, y, z] ${accel_x}, ${accel_y}, ${accel_z}")
|
||||
println("[gyro x, y, z] ${gyro_x}, ${gyro_y}, ${gyro_z}")
|
||||
println("--------------------")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* -------- 设置MPU6050 -------- */
|
||||
@C
|
||||
func mpu6050_setup(i2c_fd: Int32, addr: UInt8) {
|
||||
unsafe {
|
||||
I2cLock_lock(i2c_fd)
|
||||
setI2cAddr(i2c_fd, addr);
|
||||
sleep(Duration.millisecond * 1)
|
||||
writeI2cReg(i2c_fd, PWR_MGMT_1, PWR_NOSLEEP); /* 退出睡眠 */
|
||||
writeI2cReg(i2c_fd, GYRO_CONF_REG, GYRO_FS_500DPS); /* ±500°/s */
|
||||
writeI2cReg(i2c_fd, ACCEL_CONF_REG, ACCEL_FS_8g); /* ±8g */
|
||||
I2cLock_unlock(i2c_fd)
|
||||
sleep(Duration.millisecond * 100); /* 等待稳定 */
|
||||
}
|
||||
}
|
||||
|
||||
@C
|
||||
/* -------- 读取 16 位传感器数据(带符号)--------
|
||||
* FIXME: 是否能够正确处理符号位 */
|
||||
func read16BitReg(i2c_fd: Int32, high: UInt8, low: UInt8): Int16 {
|
||||
unsafe {
|
||||
let hh: Int16 = Int16(readI2cReg(i2c_fd, high))
|
||||
let ll: Int16 = Int16(readI2cReg(i2c_fd, low))
|
||||
return (hh << 8) | ll
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 读取传感器数据 -------- */
|
||||
@C
|
||||
func mpu6050_readSensor(i2c_fd: Int32, addr: UInt8) {
|
||||
var data = MPU6050_sensorData()
|
||||
|
||||
unsafe {
|
||||
I2cLock_lock(i2c_fd)
|
||||
setI2cAddr(i2c_fd, addr)
|
||||
data.accel_x = read16BitReg(i2c_fd, ACCEL_REG_X_HIGH, ACCEL_REG_X_LOW);
|
||||
data.accel_y = read16BitReg(i2c_fd, ACCEL_REG_Y_HIGH, ACCEL_REG_Y_LOW);
|
||||
data.accel_z = read16BitReg(i2c_fd, ACCEL_REG_Z_HIGH, ACCEL_REG_Z_LOW);
|
||||
data.gyro_x = read16BitReg(i2c_fd, GYRO_REG_X_HIGH, GYRO_REG_X_LOW);
|
||||
data.gyro_y = read16BitReg(i2c_fd, GYRO_REG_Y_HIGH, GYRO_REG_Y_LOW);
|
||||
data.gyro_z = read16BitReg(i2c_fd, GYRO_REG_Z_HIGH, GYRO_REG_Z_LOW);
|
||||
I2cLock_unlock(i2c_fd)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
/* -------- 计算PITCH角 -------- */
|
||||
//@C
|
||||
//func mpu6050_calcPitch()
|
||||
|
||||
/* -------- mpu6050主线程 --------
|
||||
* TODO(20260525-qing): 使用互补滤波就够用了 */
|
||||
const RAD_TO_DEG = 57.2956f32 /* ---- 弧度转换为角度的系数(180/π ≈ 57.2956) ---- */
|
||||
//foreign func pthread_testcancel(): Unit
|
||||
@C
|
||||
func mpu6050_main(args: CPointer<Unit>): CPointer<Unit> {
|
||||
|
||||
/* -------- 获取传入线程的参数 -------- */
|
||||
let param = unsafe{ CPointer<mpu6050_th_param>(args).read() }
|
||||
|
||||
/* -------- 为加速度计准备滤波器组 -------- */
|
||||
var kalman_accel_x = extKalman(20.0f32, 200.0f32)
|
||||
var kalman_accel_y = extKalman(20.0f32, 200.0f32)
|
||||
var kalman_accel_z = extKalman(20.0f32, 200.0f32)
|
||||
|
||||
/* -------- 为陀螺仪准备滤波器组 -------- */
|
||||
var kalman_gyro_x = extKalman(20.0f32, 200.0f32)
|
||||
var kalman_gyro_y = extKalman(20.0f32, 200.0f32)
|
||||
var kalman_gyro_z = extKalman(20.0f32, 200.0f32)
|
||||
|
||||
|
||||
while(true) {
|
||||
|
||||
println("I am running. i2c_fd: ${param.i2c_fd}, addr: ${param.addr}")
|
||||
//unsafe { _mutex_lock(mutex) }
|
||||
|
||||
/* -------- 获取传感器数据 -------- */
|
||||
var data = unsafe { mpu6050_readSensor(param.i2c_fd, param.addr) }
|
||||
|
||||
/* -------- 为加速度计执行滤波操作 -------- */
|
||||
let filtered_accel_x = kalman_accel_x.filter(Float32(data.accel_x))
|
||||
let filtered_accel_y = kalman_accel_y.filter(Float32(data.accel_y))
|
||||
let filtered_accel_z = kalman_accel_z.filter(Float32(data.accel_z))
|
||||
|
||||
/* -------- 为陀螺仪执行滤波操作 -------- */
|
||||
let filtered_gyro_x = kalman_gyro_x.filter(Float32(data.gyro_x))
|
||||
let filtered_gyro_y = kalman_gyro_y.filter(Float32(data.gyro_y))
|
||||
let filtered_gyro_z = kalman_gyro_z.filter(Float32(data.gyro_z))
|
||||
|
||||
|
||||
/* -------- 计算Pitch(俯仰角)和Roll(翻滚角) -------- */
|
||||
let roll = atan2(filtered_accel_y, filtered_accel_z) * RAD_TO_DEG
|
||||
let pitch = atan(-filtered_accel_x / sqrt(filtered_accel_y * filtered_accel_y + filtered_accel_z * filtered_accel_z)) * RAD_TO_DEG
|
||||
|
||||
let roll1 = atan(filtered_accel_y / sqrt(filtered_accel_x * filtered_accel_x + filtered_accel_z * filtered_accel_z)) * RAD_TO_DEG
|
||||
let pitch1 = atan2(-filtered_accel_x, filtered_accel_z) * RAD_TO_DEG
|
||||
|
||||
print("roll: ${roll}\t")
|
||||
println("pitch: ${pitch}")
|
||||
print("roll1: ${roll1}\t")
|
||||
println("pitch1: ${pitch1}")
|
||||
|
||||
//unsafe { _mutex_unlock(mutex) }
|
||||
sleep(Duration.millisecond * 10)
|
||||
|
||||
}
|
||||
|
||||
/* -------- 返回NULL指针以占位 -------- */
|
||||
return CPointer<Unit>()
|
||||
}
|
||||
|
||||
/* -------- mpu6050线程参数 --------
|
||||
* INFO(20260522-qing): mutex字段是一个锁的指针,需要在MPU6050的构造函数中获取资源
|
||||
* - 用于辅助别的线程获取值。 */
|
||||
@C
|
||||
struct mpu6050_th_param {
|
||||
public var i2c_fd: Int32 = 0 /* i2c文件句柄 */
|
||||
public var addr: UInt8 = 0 /* i2c设备地址 */
|
||||
public var mutex = CPointer<UInt8>() /* 用于辅助别的线程获取值的锁 */
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -------- MPU6050陀螺仪、加速度计 --------
|
||||
* - 输入并持有一个I2C文件句柄 */
|
||||
public class MPU6050 {
|
||||
|
||||
/* -------- 主初始化器 --------
|
||||
* FIXME(20260517-qing): 由于gc的原因,我们可能会手动构造RAII,
|
||||
* - 将一部分初始化移动到别的函数 */
|
||||
public init(i2c_fd: Int32, addr: UInt8) {
|
||||
this.i2c_fd = i2c_fd
|
||||
this.addr = addr
|
||||
unsafe {
|
||||
param = LibC.malloc<mpu6050_th_param>()
|
||||
var tmp = mpu6050_th_param()
|
||||
tmp.i2c_fd = i2c_fd
|
||||
tmp.addr = addr
|
||||
tmp.mutex = LibC.malloc<UInt8>()
|
||||
param.write(tmp)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 终结器 -------- */
|
||||
~init() {
|
||||
/* -------- 清除构造时申请的线程参数和锁 -------- */
|
||||
unsafe {
|
||||
var tmp = param.read()
|
||||
LibC.free(tmp.mutex)
|
||||
LibC.free(param)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 初始化该模块 -------- */
|
||||
public func setup() {
|
||||
unsafe { mpu6050_setup(i2c_fd, addr) }
|
||||
}
|
||||
|
||||
/*----------------*/
|
||||
private var i2c_fd: Int32 /* -------- I2C设备文件句柄 -------- */
|
||||
private var addr: UInt8 /* -------- I2C设备地址 -------- */
|
||||
|
||||
|
||||
/* -------- 获取原始MPU6050传感器数据 -------- */
|
||||
public func readSensor(): MPU6050_sensorData {
|
||||
return unsafe { mpu6050_readSensor(i2c_fd, addr) }
|
||||
}
|
||||
|
||||
/* -------- mpu6050启动守护 --------
|
||||
* TODO(20260520-qing): 放进类中
|
||||
* --------------------------------- */
|
||||
var param: CPointer<mpu6050_th_param> /* -------- 线程参数必须能够持久保存 -------- */
|
||||
var pth: Option<Pth> = None /* -------- 线程对象 -------- */
|
||||
public func startDaemon() { /* -------- 启动线程 -------- */
|
||||
pth = Pth(mpu6050_main, CPointer<Unit>(param))
|
||||
}
|
||||
|
||||
/* -------- 停止线程 -------- */
|
||||
public func stopDaemon() {
|
||||
if (let Some(P) <- pth) {
|
||||
P.close() }
|
||||
}
|
||||
|
||||
/* -------- 等待线程终止 -------- */
|
||||
public func waitDaemon() {
|
||||
if (let Some(P) <- pth) {
|
||||
var ptr: CPointer<Unit> = CPointer<Unit>()
|
||||
P.join(CPointer<CPointer<Unit>>(CPointer<CPointer<Unit>>(ptr))) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package bot4cj.hd
|
||||
import pthread4cj.*
|
||||
|
||||
/* pca9685舵机控制版 - bot4cj
|
||||
* TODO(20260515-qing): 由于对统一接口的需求,设计基于回调的虚拟化硬件
|
||||
* TODO(20260516-qing): 获取pca9685的中文或者英文参考资料
|
||||
* NOTE(20260618-qing): 该代码已经被Lua脚本所取代,留着备用说不定以后会删除。 */
|
||||
public class PCA9685 {
|
||||
private static const ADDR_0: UInt8 = 0x40 /* PCA9685的i2c地址 */
|
||||
private let i2c_fd: Int32
|
||||
private let addr: UInt8
|
||||
public init( i2c_fd: Int32,
|
||||
addr!: UInt8 = ADDR_0) { /* PCA9685硬件地址 */
|
||||
this.i2c_fd = i2c_fd
|
||||
this.addr = addr
|
||||
/* -------- 初始化PCA9685 --------
|
||||
* FIXME(20260517-qing): 在旧版机器人代码移除以后,移除这一段 */
|
||||
|
||||
}
|
||||
|
||||
/* -------- 设置PWM频率为50Hz(舵机标准周期20ms0) --------
|
||||
* TODO(20260516-qing): 不将数值写死 */
|
||||
public func setup() {
|
||||
unsafe {
|
||||
I2cLock_lock(i2c_fd)
|
||||
setI2cAddr(i2c_fd, addr);
|
||||
writeI2cReg(i2c_fd, 0x00, 0x20); /* 开启AI(地址自增)和关闭SLEEP */
|
||||
sleep(5000 * Duration.nanosecond); /* 等待振荡器启动 */
|
||||
/* FIXME: 这一条是不是重复了 */
|
||||
var freq: Float32 = 50.0;
|
||||
var prescale_val: Float32 = 25000000.0 / (4096.0 * freq) - 1.0; /* 25MHz主频 */
|
||||
var prescale: UInt8 = UInt8(prescale_val + 0.5);
|
||||
|
||||
writeI2cReg(i2c_fd, 0x00, 0x10); /* 进入SLEEP模式(允许改频率) */
|
||||
writeI2cReg(i2c_fd, 0xFE, prescale); /* 写入预分频值 */
|
||||
writeI2cReg(i2c_fd, 0x00, 0x20); /* 退出SLEEP */
|
||||
|
||||
I2cLock_unlock(i2c_fd)
|
||||
}
|
||||
sleep(5000 * Duration.nanosecond); /* 等待振荡器启动 */
|
||||
}
|
||||
|
||||
|
||||
/* -------- 设置指定通道的PWM信号(0-4095)-------- */
|
||||
public func setPWM(channel: UInt8, on: UInt16, off: UInt16) {
|
||||
var reg: UInt8 = 0x06 + 4 * channel; /* LED0 寄存器起始地址 */
|
||||
unsafe {
|
||||
writeI2cReg(i2c_fd, reg, UInt8(on & 0xFF));
|
||||
writeI2cReg(i2c_fd, reg + 1, UInt8(on >> 8));
|
||||
writeI2cReg(i2c_fd, reg + 2, UInt8(off & 0xFF));
|
||||
writeI2cReg(i2c_fd, reg + 3, UInt8(off >> 8));
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/* -------- 角度转PWM值(0°~180° → 0.5ms~2.5ms脉宽) -------- */
|
||||
public func setAngle(channel: UInt8, angle: Float32) {
|
||||
const min: Float32 = 0.5; /* 0°对应0.5ms */
|
||||
const max: Float32 = 2.5; /* 180°对应2.5ms */
|
||||
let width: Float32 = min + (max - min) * (angle / 180.0);
|
||||
let off_val: UInt16 = UInt16(width * 4096.0 / 20.0); /* 20ms 周期 */
|
||||
unsafe {
|
||||
I2cLock_lock(i2c_fd)
|
||||
setI2cAddr(i2c_fd, this.addr);
|
||||
this.setPWM(channel, 0, off_val); /* ON=0, OFF=计算值 */
|
||||
I2cLock_unlock(i2c_fd)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package bot4cj.hd
|
||||
import std.math.*
|
||||
/* -------- 舵机类 --------
|
||||
* 含最大值、最小值、漂移值三个调整项
|
||||
* 以及一个设置角度回调函数
|
||||
*
|
||||
* FIXME(20260618-qing): 在构造函数中设置角度最大值 */
|
||||
public class Servo {
|
||||
private var max: Float32
|
||||
private var min: Float32
|
||||
private var offset: Float32
|
||||
private var callback_setAngle: (Float32) -> Unit
|
||||
private static let MAX_ANGLE: Float32 = 180.0f32
|
||||
public init(max!: Float32, min!: Float32, offset!: Float32,
|
||||
callback_setAngle!: (Float32) -> Unit) {
|
||||
this.max =max
|
||||
this.min = min
|
||||
this.offset = offset
|
||||
this.callback_setAngle = callback_setAngle
|
||||
}
|
||||
|
||||
public func setAngle(angle: Float32) {
|
||||
var a = angle
|
||||
a += offset /* ---- 加上漂移值 ---- */
|
||||
if (a > abs(max)) { a = abs(max)}
|
||||
if (a < abs(min)) { a = abs(min)}
|
||||
if (max < 0.0f32) { a = MAX_ANGLE - a }
|
||||
callback_setAngle(a)
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
macro package bot4cj.hd.extern
|
||||
|
||||
import std.fs.*
|
||||
import std.io.*
|
||||
import std.ast.*
|
||||
|
||||
public macro ExternHardware(attrTokens: Tokens, inputTokens: Tokens): Tokens {
|
||||
let expr = LitConstExpr(attrTokens)
|
||||
let file = File(expr.literal.value, OpenMode.Read)
|
||||
let bytes = readToEnd(file)
|
||||
file.close()
|
||||
let sb = StringBuilder()
|
||||
sb.appendFromUtf8(bytes)
|
||||
let code = sb.toString()
|
||||
return cangjieLex(code)
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package bot4cj.hd
|
||||
import ctype4cj.*
|
||||
|
||||
const FBIOGET_VSCREENINFO = 0x4602u64
|
||||
const FBIOGET_FSCREENINFO = 0x4600u64
|
||||
const PROT_WRITE: C_Int = 0x2
|
||||
const MAP_SHARED: C_Int = 0x01
|
||||
|
||||
/* --- 定义在I2C.cj里了 ---*/
|
||||
//foreign func ioctl(fd: C_Int, request: C_ULong, ...): C_Int
|
||||
/* ---- 内存映射 ---- */
|
||||
public type off_t = C_LongLong
|
||||
foreign func mmap(addr: CPointer<Unit>, length: UIntNative, prot: C_Int, flags: C_Int, fd: C_Int, offset: off_t): CPointer<Unit>
|
||||
|
||||
/* -------- 打开framebuffer帧缓冲设备 -------- */
|
||||
@C
|
||||
public func fb_open(fb_dev: CString): C_Int {
|
||||
return unsafe { libc_open(fb_dev, O_RDWR) }
|
||||
}
|
||||
|
||||
/* -------- 帧缓冲区固定参数 -------- */
|
||||
@C
|
||||
public struct fb_fix_screeninfo {
|
||||
var id: VArray<C_Char, $16> /* 设备驱动名称字符串,比如"TT Builtin" */
|
||||
var smem_start: C_ULong = 0 /* 帧缓冲内存(显存)的物理起始地址 */
|
||||
/* (physical address) */
|
||||
var smem_len: UInt32 = 0 /* 帧缓冲内存的总长度,单位字节 */
|
||||
var type_: UInt32 = 0 /* 显存类型(fb存储布局)see FB_TYPE_* */
|
||||
var type_aux: UInt32 = 0 /* 显存附加类型 */
|
||||
var visual: UInt32 = 0 /* 色彩显示模式 see FB_VISUAL_* */
|
||||
var xpanstep: UInt16 = 0 /* zero if no hardware panning */
|
||||
var ypanstep: UInt16 = 0 /* zero if no hardware panning */
|
||||
var ywrapstep: UInt16 = 0 /* zero if no hardware ywrap */
|
||||
var line_length: UInt32 = 0 /* 显示屏上一行像素数据所占用的字节数 */
|
||||
var mmio_start: C_ULong = 0 /* 内存映射I/O的物理起始地址 */
|
||||
/* (physical address) */
|
||||
var mmio_len: UInt32 = 0 /* 内存映射的区域长度,单位字节 */
|
||||
var accel: UInt32 = 0 /* 硬件加速类型 */
|
||||
/* specific chip/card we have */
|
||||
var capabilities: UInt16 = 0 /* see FB_CAP_* */
|
||||
var reserved: VArray<UInt16, $2> /* 保留字段,用于未来扩展,确保兼容 */
|
||||
|
||||
public init() {
|
||||
id = VArray<C_Char, $16> (repeat: 0)
|
||||
reserved = VArray<UInt16, $2> (repeat: 0)
|
||||
}
|
||||
}
|
||||
|
||||
/* 颜色字段偏移量的解释:所有偏移量均从右侧开始计算,
|
||||
* 位于一个“像素”值内部,该像素值的宽度正好等于 'bits_per_pixel'
|
||||
* (也就是说:你可以将偏移量作为 << 的右操作数使用)。
|
||||
* 之后的像素就是一个比特流,会原封不动地写入显存。
|
||||
*
|
||||
* 对于伪彩色:所有颜色分量的偏移量和长度应该相同。
|
||||
* 偏移量指定了调色板索引的最低有效位在像素值中的位置。
|
||||
* 长度表示可用的调色板条目数量(即条目数 = 1 << 长度)。
|
||||
*/
|
||||
@C
|
||||
public struct fb_bitfield {
|
||||
var offset: UInt32 = 0 /* 位域的起始位置(从右侧算起)*/
|
||||
var length: UInt32 = 0 /* 位域的长度(比特数) */
|
||||
var msb_right: UInt32 = 0 /* != 0 : 最高有效位在右侧 */
|
||||
/* (即低位在右) */
|
||||
}
|
||||
|
||||
/* -------- 帧缓冲区可变参数 -------- */
|
||||
@C
|
||||
public struct fb_var_screeninfo {
|
||||
var xres: UInt32 = 0 /* visible resolution */
|
||||
var yres: UInt32 = 0
|
||||
var xres_virtual: UInt32 = 0 /* virtual resolution */
|
||||
var yres_virtual: UInt32 = 0
|
||||
var xoffset: UInt32 = 0 /* offset from virtual to visible */
|
||||
var yoffset: UInt32 = 0 /* resolution */
|
||||
|
||||
var bits_per_pixel: UInt32 = 0 /* guess what */
|
||||
var grayscale: UInt32 = 0 /* 0 = color, 1 = grayscale, */
|
||||
/* >1 = FOURCC */
|
||||
var red: fb_bitfield /* bitfield in fb mem if true color, */
|
||||
var green: fb_bitfield /* else only length is significant */
|
||||
var blue: fb_bitfield
|
||||
var transp: fb_bitfield /* transparency */
|
||||
|
||||
var nonstd: UInt32 = 0 /* != 0 Non standard pixel format */
|
||||
|
||||
var activate: UInt32 = 0 /* see FB_ACTIVATE_* */
|
||||
|
||||
var height: UInt32 = 0 /* height of picture in mm */
|
||||
var width: UInt32 = 0 /* width of picture in mm */
|
||||
|
||||
var accel_flags: UInt32 = 0 /* (OBSOLETE) see fb_info.flags */
|
||||
|
||||
/* Timing: All values in pixclocks, except pixclock (of course) */
|
||||
var pixclock: UInt32 = 0 /* pixel clock in ps (pico seconds) */
|
||||
var left_margin: UInt32 = 0 /* time from sync to picture */
|
||||
var right_margin: UInt32 = 0 /* time from picture to sync */
|
||||
var upper_margin: UInt32 = 0 /* time from sync to picture */
|
||||
var lower_margin: UInt32 = 0
|
||||
var hsync_len: UInt32 = 0 /* length of horizontal sync */
|
||||
var vsync_len: UInt32 = 0 /* length of vertical sync */
|
||||
var sync: UInt32 =0 /* see FB_SYNC_* */
|
||||
var vmode: UInt32 = 0 /* see FB_VMODE_* */
|
||||
var rotate: UInt32 = 0 /* angle we rotate counter clockwise */
|
||||
var colorspace: UInt32 = 0 /* colorspace for FOURCC-based modes */
|
||||
var reserved: VArray<UInt32, $4>/* Reserved for future compatibility */
|
||||
|
||||
public init() {
|
||||
red = fb_bitfield()
|
||||
green = fb_bitfield()
|
||||
blue = fb_bitfield()
|
||||
transp = fb_bitfield()
|
||||
reserved = VArray<UInt32, $4> (repeat: 0)
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 获取帧缓冲区可变参数 ---- */
|
||||
@C
|
||||
public func fb_get_var_info(fd: C_Int) {
|
||||
var info = fb_var_screeninfo()
|
||||
unsafe { ioctl(fd, FBIOGET_VSCREENINFO, inout info) }
|
||||
return info
|
||||
}
|
||||
|
||||
/* ---- 获取帧缓冲区固定参数 ---- */
|
||||
@C
|
||||
public func fb_get_fix_info(fd: C_Int) {
|
||||
var info = fb_fix_screeninfo()
|
||||
unsafe { ioctl(fd, FBIOGET_FSCREENINFO, inout info) }
|
||||
return info
|
||||
}
|
||||
|
||||
/* ---- 内存映射 ---- */
|
||||
@C
|
||||
public func fb_mmap(addr: CPointer<Unit>, length: UIntNative, fd: C_Int, offset: off_t): CPointer<Unit>
|
||||
{
|
||||
let buf = unsafe { mmap(addr, length, PROT_WRITE, MAP_SHARED, fd, offset) }
|
||||
return buf
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package bot4cj.hd
|
||||
import std.time.*
|
||||
//foreign func play_frequency(frequency: Int32, duration_ms: Int32, sample_rate: Int32): Bool
|
||||
//foreign func play_frequency(frequency: Float32, duration_ms: Int32, volume: Float64, channels: Int32, sample_rate: Int32): Bool
|
||||
|
||||
/* -------- 扬声器类 --------
|
||||
* TODO: 一次打开设备,一段时间后再关闭 */
|
||||
public class Spk {
|
||||
private var channels: Int32
|
||||
private var sample_rate: Int32
|
||||
public init(channels!: Int32, sample_rate!: Int32) {
|
||||
this.channels = channels
|
||||
this.sample_rate = sample_rate
|
||||
}
|
||||
|
||||
/* -------- 控制扬声器发声 -------- */
|
||||
public func play(frequency: Float32, volume: Float64, durations_ms: Int32): Bool {
|
||||
//unsafe { play_frequency(frequency, durations_ms, volume, channels, sample_rate) }
|
||||
sleep(Duration.millisecond * Int64(durations_ms))
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package bot4cj
|
||||
import std.io.*
|
||||
import std.fs.*
|
||||
import std.env.*
|
||||
import std.time.*
|
||||
import std.argopt.*
|
||||
import std.convert.*
|
||||
import std.collection.*
|
||||
import stdx.encoding.json.*
|
||||
import bot4cj.hd.*
|
||||
import bot4cj.math.*
|
||||
import ctype4cj.*
|
||||
|
||||
/* ---- 神经网络输出路径 ---- */
|
||||
var NNS_OUT: Option<String> = None
|
||||
main(args: Array<String>): Int64 {
|
||||
|
||||
/* ---- 解析命令行参数 ---- */
|
||||
let specs = [
|
||||
ArgumentSpec.Long("help", ArgumentMode.NoValue), /* ---- 拉起帮助菜单 ---- */
|
||||
ArgumentSpec.Short(r'i', ArgumentMode.RequiredValue), /* ---- 神经网络模型 - 输入 ---- */
|
||||
ArgumentSpec.Short(r'o', ArgumentMode.RequiredValue), /* ---- 神经网络模型 - 输出 ---- */
|
||||
ArgumentSpec.Long("test", ArgumentMode.RequiredValue) /* ---- 运行一个测试分支 - 需要输入测试类型 ---- */
|
||||
]
|
||||
|
||||
/* ---- 执行解析 ---- */
|
||||
let parsed = parseArguments(args, specs)
|
||||
let options = parsed.options
|
||||
|
||||
//---------------------------
|
||||
/* 读取设置项 */
|
||||
|
||||
/*---------------------------*/
|
||||
/*---------------------------*/
|
||||
/* 初始化神经网络 (init nn)
|
||||
* TODO: 可以去掉内部初始化参数,使用矩阵式模板进行初始化 */
|
||||
|
||||
|
||||
//var nns: ArrayList<Nn> =
|
||||
//if (options.keys().contains("i")) {
|
||||
// let filename = options['i']
|
||||
// load_nns(filename) }
|
||||
//else {
|
||||
// let nn_in = ArrayList<Nn>()
|
||||
// nn_in.add(Nn(41, 80, ActivationFuncs.Leaky_ReLU, 0.2f32))
|
||||
// nn_in.add(Nn(80, 80, ActivationFuncs.Leaky_ReLU, 0.2f32))
|
||||
// nn_in.add(Nn(80, 40, ActivationFuncs.Leaky_ReLU, 0.2f32))
|
||||
// nn_in.add(Nn(40, 17, ActivationFuncs.Sigmoid, 0.2f32)) // 16个舵机加1个时延
|
||||
// nn_in }
|
||||
|
||||
/* ---- 使用-o选项进行输出路径的选择 ---- */
|
||||
if (options.contains("o")) {
|
||||
NNS_OUT = Option<String>.Some(options['o'])
|
||||
}
|
||||
|
||||
/*---------------------------*/
|
||||
/*---------------------------*/
|
||||
/* 分支选择 */
|
||||
|
||||
|
||||
/* -------- 拉起帮助手册
|
||||
* -------- */
|
||||
if (options.contains("help")) {
|
||||
print_help()
|
||||
return 0
|
||||
}
|
||||
|
||||
/* -------- 启动一个测试分支
|
||||
* -------- */
|
||||
else if (options.contains("test")) {
|
||||
run_test(options["test"])
|
||||
return 0
|
||||
}
|
||||
|
||||
/* -------- 缺省行为:从标准输入获取动作脚本
|
||||
* --------
|
||||
* -------- TODO(20260519-qing): 进行脚本引入(相对路径也可,从**环境变量**的路径中搜寻也可) */
|
||||
else {
|
||||
let consoleReader = getStdIn()
|
||||
let robot = RobotStruct(consoleReader)
|
||||
try {
|
||||
robot.active()
|
||||
robot.run()
|
||||
}
|
||||
finally {
|
||||
robot.deactive()
|
||||
}
|
||||
|
||||
/* ---- NOTE(20260523-qing): 储存微调网络模型
|
||||
* ---- */
|
||||
// let consoleReader = getStdIn()
|
||||
// run_action_drl(consoleReader, nns)
|
||||
// if (options.contains("o")) {
|
||||
// save_nns(nns, options['o']) }
|
||||
}
|
||||
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
/* -------- 打印帮助手册
|
||||
* -------- */
|
||||
func print_help()
|
||||
{
|
||||
println("这是一个机器人动作脚本解析器。")
|
||||
println("使用管道输入动作脚本,示例:")
|
||||
println("cat std.mt | ./bot")
|
||||
println()
|
||||
println("--i2c=/path/to/device\t选择i2c设备")
|
||||
println()
|
||||
println("-i 选择输入的模型的路径")
|
||||
println("-o 设置输出的模型的路径")
|
||||
}
|
||||
|
||||
/* -------- 集成多种常规测试、临时测试
|
||||
* -------- */
|
||||
func run_test(key: String) {
|
||||
|
||||
/* ----------- 神经网络与门测试
|
||||
* -----------
|
||||
* ----------- INFO: 我们使用随机生成的动态数据集,这样会导致一些后果 */
|
||||
if (key == "nn") {
|
||||
let nn_t = ArrayList<Nn1>()
|
||||
nn_t.add(Nn1(2, 4, 0.05f32, Af.Leaky_ReLU(FP32(0.01))))
|
||||
nn_t.add(Nn1(4, 1, 0.05f32, Af.Sigmoid))
|
||||
let batch = 1
|
||||
let num =2000
|
||||
let prt_num = 100
|
||||
for (i in 0..(num*batch)) {
|
||||
let m = Matrix1(1, 2)
|
||||
let t = Matrix1(1, 1)
|
||||
prepare_andDoor(m, t)
|
||||
let l = train1(nn_t, m, t, 1.0f32) // 这个1.0是学习率的折扣,而非学习率本身
|
||||
if (i % prt_num == prt_num-1) {
|
||||
print("${i}\t:")
|
||||
print("${l}")
|
||||
println()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
else if (key == "cnn") {
|
||||
testCNN()
|
||||
}
|
||||
|
||||
else if (key == "cnn1") {
|
||||
testGeometryCNN()
|
||||
}
|
||||
|
||||
|
||||
/* -------- 测试解析矩阵,并且输出
|
||||
* -------- */
|
||||
else if (key == "matrix_o") {
|
||||
let file = File("ss/test.mt", OpenMode.Read)
|
||||
let robot = RobotStruct(file)
|
||||
file.close()
|
||||
let outf = File("ss/output.mt", OpenMode.Write)
|
||||
robot.save(outf)
|
||||
outf.close()
|
||||
}
|
||||
|
||||
/* -------- 解析测试输出的矩阵
|
||||
* -------- */
|
||||
else if (key == "matrix_2") {
|
||||
let file = File("ss/output.mt", OpenMode.Read)
|
||||
let robot = RobotStruct(file)
|
||||
file.close()
|
||||
robot.active()
|
||||
robot.run()
|
||||
robot.deactive()
|
||||
let outf = File("ss/output_1.mt", OpenMode.Write)
|
||||
robot.save(outf)
|
||||
outf.close()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// Adam.cj - Adam 优化器实现(添加全连接层支持)
|
||||
package bot4cj.math
|
||||
|
||||
import std.math.*
|
||||
import std.collection.*
|
||||
|
||||
public class Adam {
|
||||
let learningRate: FP32
|
||||
let beta1: FP32
|
||||
let beta2: FP32
|
||||
let epsilon: FP32
|
||||
|
||||
var m: HashMap<String, Matrix1>
|
||||
var v: HashMap<String, Matrix1>
|
||||
var t: Int64
|
||||
|
||||
// 完整参数构造函数
|
||||
public init(
|
||||
lr: FP32,
|
||||
beta1: FP32,
|
||||
beta2: FP32,
|
||||
epsilon: FP32
|
||||
) {
|
||||
learningRate = lr
|
||||
this.beta1 = beta1
|
||||
this.beta2 = beta2
|
||||
this.epsilon = epsilon
|
||||
m = HashMap<String, Matrix1>()
|
||||
v = HashMap<String, Matrix1>()
|
||||
t = 0
|
||||
}
|
||||
|
||||
// 默认参数构造函数
|
||||
public init() {
|
||||
learningRate = FP32(0.001)
|
||||
beta1 = FP32(0.9)
|
||||
beta2 = FP32(0.999)
|
||||
epsilon = FP32(0.00000001)
|
||||
m = HashMap<String, Matrix1>()
|
||||
v = HashMap<String, Matrix1>()
|
||||
t = 0
|
||||
}
|
||||
|
||||
// 仅自定义学习率的构造函数
|
||||
public init(lr: FP32) {
|
||||
learningRate = lr
|
||||
beta1 = FP32(0.9)
|
||||
beta2 = FP32(0.999)
|
||||
epsilon = FP32(0.00000001)
|
||||
m = HashMap<String, Matrix1>()
|
||||
v = HashMap<String, Matrix1>()
|
||||
t = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算幂运算的辅助函数
|
||||
*/
|
||||
private func power(base: FP32, exp: Int64): FP32 {
|
||||
var result = FP32(1.0)
|
||||
for (i in 0..exp) {
|
||||
result = result * base
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算平方根的辅助函数 - 使用牛顿迭代法
|
||||
*/
|
||||
private func sqrt(val: FP32): FP32 {
|
||||
let x = val.getValue()
|
||||
if (x <= 0.0) {
|
||||
return FP32(0.0)
|
||||
}
|
||||
var guess = x
|
||||
for (i in 0..10) {
|
||||
guess = (guess + x / guess) / 2.0
|
||||
}
|
||||
return FP32(guess)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新矩阵参数(通用方法)
|
||||
* @param param 要更新的参数矩阵
|
||||
* @param grad 梯度矩阵
|
||||
* @param key 参数在HashMap中的键名
|
||||
*/
|
||||
private func updateMatrix(
|
||||
param: Matrix1,
|
||||
grad: Matrix1,
|
||||
key: String
|
||||
): Unit {
|
||||
// 初始化动量
|
||||
let hasState = m.contains(key)
|
||||
if (!hasState) {
|
||||
let rows = param.get_row()
|
||||
let cols = param.get_col()
|
||||
m.add(key, Matrix1(rows, cols))
|
||||
v.add(key, Matrix1(rows, cols))
|
||||
}
|
||||
|
||||
let mMat = m.get(key).getOrThrow()
|
||||
let vMat = v.get(key).getOrThrow()
|
||||
|
||||
let rows = param.get_row()
|
||||
let cols = param.get_col()
|
||||
|
||||
for (r in 0..rows) {
|
||||
for (c in 0..cols) {
|
||||
let g = grad.get(r, c).getOrThrow()
|
||||
|
||||
// 更新一阶矩
|
||||
let oldM = mMat.get(r, c).getOrThrow()
|
||||
let newM = beta1 * oldM + (FP32(1.0) - beta1) * g
|
||||
mMat.set(r, c, newM)
|
||||
|
||||
// 更新二阶矩
|
||||
let oldV = vMat.get(r, c).getOrThrow()
|
||||
let newV = beta2 * oldV + (FP32(1.0) - beta2) * g * g
|
||||
vMat.set(r, c, newV)
|
||||
|
||||
// 偏差修正
|
||||
let one = FP32(1.0)
|
||||
let beta1Pow = power(beta1, t)
|
||||
let beta2Pow = power(beta2, t)
|
||||
|
||||
let mHat = newM / (one - beta1Pow)
|
||||
let vHat = newV / (one - beta2Pow)
|
||||
|
||||
// 更新参数
|
||||
let oldParam = param.get(r, c).getOrThrow()
|
||||
let update = learningRate * mHat / (sqrt(vHat) + epsilon)
|
||||
param.set(r, c, oldParam - update)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新卷积层参数
|
||||
*/
|
||||
public func updateConvLayer(layer: ConvLayer): Unit {
|
||||
t += 1
|
||||
|
||||
// 更新卷积核
|
||||
var kGrad = match (layer.kernelsGrad) {
|
||||
case Some(val) => val
|
||||
case _ => throw Exception("No kernel gradients")
|
||||
}
|
||||
|
||||
for (oc in 0..layer.nOutputChannels) {
|
||||
let key = "conv_kernel_" + oc.toString()
|
||||
updateMatrix(layer.kernels[oc], kGrad[oc], key)
|
||||
}
|
||||
|
||||
// 更新偏置
|
||||
var bGrad = match (layer.biasGrad) {
|
||||
case Some(val) => val
|
||||
case _ => throw Exception("No bias gradient")
|
||||
}
|
||||
|
||||
let biasKey = "conv_bias"
|
||||
updateMatrix(layer.biases, bGrad, biasKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新全连接层参数(Nn1)
|
||||
*/
|
||||
public func updateFCLayer(layer: Nn1): Unit {
|
||||
t += 1
|
||||
|
||||
// 更新权重
|
||||
var wGrad = match (layer.weights_grad) {
|
||||
case Some(val) => val
|
||||
case _ => throw Exception("No weights gradient")
|
||||
}
|
||||
|
||||
let weightKey = "fc_weights"
|
||||
updateMatrix(layer.weights, wGrad, weightKey)
|
||||
|
||||
// 更新偏置
|
||||
var bGrad = match (layer.bias_grad) {
|
||||
case Some(val) => val
|
||||
case _ => throw Exception("No bias gradient")
|
||||
}
|
||||
|
||||
let biasKey = "fc_bias"
|
||||
updateMatrix(layer.bias, bGrad, biasKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置优化器状态
|
||||
*/
|
||||
public func reset(): Unit {
|
||||
m.clear()
|
||||
v.clear()
|
||||
t = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前迭代次数
|
||||
*/
|
||||
public func getStep(): Int64 {
|
||||
t
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package bot4cj.math
|
||||
import std.math.*
|
||||
|
||||
/* -------- Activation Functions 激活函数族 --------
|
||||
* TODO(20260516-qing): 增加序列化、反序列化工厂函数 */
|
||||
public enum Af {
|
||||
|
||||
None | Sigmoid | ReLU | Leaky_ReLU(FP32) | Tanh | Softmax
|
||||
|
||||
/* -------- 执行激活函数 -------- */
|
||||
public func forward(x: Matrix1) {
|
||||
match(this) {
|
||||
case None => none(x)
|
||||
case Sigmoid => sigmoid(x)
|
||||
case ReLU => relu(x)
|
||||
case Leaky_ReLU(alpha) => leaky_relu(x, alpha)
|
||||
case Tanh => tanh(x)
|
||||
case Softmax => softmax(x)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 激活函数的导数 -------- */
|
||||
public func backward(y: Matrix1) {
|
||||
match(this) {
|
||||
case None => none_dot(y)
|
||||
case Sigmoid => sigmoid_dot(y)
|
||||
case ReLU => relu_dot(y)
|
||||
case Leaky_ReLU(alpha) => leaky_relu_dot(y, alpha)
|
||||
case Tanh => tanh_dot(y)
|
||||
case Softmax => softmax_jacobian(y)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 序列化与反序列化 -------- */
|
||||
public func get_type(): String {
|
||||
return match(this) {
|
||||
case None => "None"
|
||||
case Sigmoid => "Sigmoid"
|
||||
case ReLU => "ReLU"
|
||||
case Leaky_ReLU(alpha) => "Leaky_ReLU"
|
||||
case Tanh => "Tanh"
|
||||
case Softmax => "Softmax"
|
||||
}
|
||||
}
|
||||
|
||||
/* */
|
||||
public func get_value(): FP32 {
|
||||
return match(this) {
|
||||
case None => FP32(0.0f32)
|
||||
case Sigmoid => FP32(0.0f32)
|
||||
case ReLU => FP32(0.0f32)
|
||||
case Leaky_ReLU(alpha) => alpha
|
||||
case Tanh => FP32(0.0f32)
|
||||
case Softmax => FP32(0.0f32)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 空的激活函数 -------- */
|
||||
private static func none(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
N.set(r, c, FP32(x1)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 空激活函数的导数 -------- */
|
||||
private static func none_dot(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
N.set(r, c, FP32(1.0f32)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- S型生长曲线 -------- */
|
||||
private static func sigmoid(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 = 1.0f32 / (1.0f32 + exp(-x1))
|
||||
N.set(r, c, FP32(x2)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- S型生长曲线的导数 -------- */
|
||||
private static func sigmoid_dot(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 = 1.0f32 / (1.0f32 + exp(-x1))
|
||||
let x3 = x2 * (1.0f32 - x2)
|
||||
N.set(r, c, FP32(x3)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 线性整流单元 -------- */
|
||||
private static func relu(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 =if (x1 > 0.0f32) {x1} else{0.0f32}
|
||||
N.set(r, c, FP32(x2))
|
||||
}
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 线性整流单元的导数 -------- */
|
||||
private static func relu_dot(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 =if (x1 > 0.0f32) {1.0f32} else{0.0f32}
|
||||
N.set(r, c, FP32(x2))
|
||||
}
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 带泄露的线性整流单元 -------- */
|
||||
private static func leaky_relu(M: Matrix1, alpha: FP32) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 =if (x1 > 0.0f32) {x1} else{x1 * alpha.getValue()}
|
||||
N.set(r, c, FP32(x2))
|
||||
}
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 带泄露的整流单元导数 -------- */
|
||||
private static func leaky_relu_dot(M: Matrix1, alpha: FP32) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 =if (x1 > 0.0f32) { 1.0f32 } else{alpha.getValue()}
|
||||
N.set(r, c, FP32(x2)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 双曲正切函数 --------*/
|
||||
private static func tanh(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 = tanh(x1)
|
||||
N.set(r, c, FP32(x2)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 双曲正切函数的导数 -------- */
|
||||
private static func tanh_dot(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let y = M.get(r, c).getOrThrow()
|
||||
let y_val= y.getValue() /*---- NOTE(20260525-qing): 修复命名(x => y) ----*/
|
||||
let rea = 1.0f32 - y_val * y_val
|
||||
N.set(r, c, FP32(rea)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 归一化指数函数 -------- */
|
||||
private static func softmax(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
var max_val = 0.0f32
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
if (x1 > max_val) {
|
||||
max_val = x1 }
|
||||
}
|
||||
}
|
||||
var sum = 0.0f32
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 = exp(x1 - max_val)
|
||||
N.set(r, c, FP32(x2))
|
||||
sum += x2
|
||||
}
|
||||
}
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = N.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 = x1 / sum
|
||||
N.set(r, c, FP32(x2))
|
||||
}
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 计算Softmax的雅可比矩阵 --------
|
||||
* 输入:已计算好的softmax输出(概率分布)
|
||||
* 输出:雅可比矩阵 J[i][j] = ∂y_i/∂x_j */
|
||||
private func softmax_jacobian(softmax_output: Matrix1) {
|
||||
let row = softmax_output.get_row()
|
||||
let col = softmax_output.get_col()
|
||||
let n = row * col
|
||||
var jacobian = Matrix1(n, n)
|
||||
|
||||
for (i in 0..n) {
|
||||
for (j in 0..n) {
|
||||
|
||||
let row_i = i / col /* 需要将一维索引转换为二维索引来访问Matrix1 */
|
||||
let col_i = i % col
|
||||
let row_j = j / col
|
||||
let col_j = j % col
|
||||
|
||||
let val_i = softmax_output.get(row_i, col_i).getOrThrow().getValue()
|
||||
let val_j = softmax_output.get(row_j, col_j).getOrThrow().getValue()
|
||||
|
||||
if (i == j) { /* 对角线元素: y_i * (1 - y_i) */
|
||||
jacobian.set(i, j, FP32(val_i * (1.0f32 - val_i))) }
|
||||
else { /* 非对角线元素: -y_i * y_j */
|
||||
jacobian.set(i, j, FP32(-val_i * val_j)) }
|
||||
}
|
||||
}
|
||||
|
||||
return jacobian
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package bot4cj.math
|
||||
import std.random.*
|
||||
import std.time.*
|
||||
import std.math.*
|
||||
|
||||
/* -------- 神经网络使用的权重矩阵初始化类 --------
|
||||
* TODO(20260517-qing): 将该函数改为枚举类,统一一个接口,根据模式匹配执行初始化
|
||||
* - 这样在矩阵式中可以定义初始化方式 */
|
||||
public class Ini {
|
||||
|
||||
/* -------- xavier正态初始化 -------- */
|
||||
public static func xavierNormalInit(target: Matrix1, fan_in: Int64, fan_out: Int64) {
|
||||
let seed = DateTime.now().nanosecond
|
||||
let random = Random(UInt64(seed))
|
||||
let sigma = sqrt(2.0f32 / Float32(fan_in + fan_out)) /* 计算标准差 */
|
||||
for (r in 0..fan_in) {
|
||||
for (c in 0..fan_out) {
|
||||
let v = random.nextGaussianFloat32(mean: 0.0f32, sigma:sigma)
|
||||
target.set(r, c, FP32(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- Glorot均匀初始化 -------- */
|
||||
public static func xavierUniformInit(target: Matrix1, fan_in: Int64, fan_out: Int64) {
|
||||
let limit = sqrt(6.0f32 / Float32(fan_in + fan_out))
|
||||
let seed = DateTime.now().nanosecond
|
||||
let random = Random(UInt64(seed))
|
||||
|
||||
for (r in 0..fan_in) {
|
||||
for (c in 0..fan_out) {
|
||||
let v = (random.nextFloat32() * 2.0f32 - 1.0f32) * limit /* 均匀分布 [-limit, limit] */
|
||||
target.set(r, c, FP32(v)) }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
package bot4cj.math
|
||||
import std.fs.*
|
||||
import std.io.*
|
||||
import std.convert.*
|
||||
//foreign func createMatrix(row: UInt64, col: UInt64): CPointer<Unit>
|
||||
//foreign func freeMatrix(M: CPointer<Unit>): Unit
|
||||
//foreign func witchMatrix(M: CPointer<Unit>, row: UInt64, col: UInt64): CPointer<Float32>
|
||||
//foreign func getMatrix(M: CPointer<Unit>, row: UInt64, col: UInt64): Float32
|
||||
//foreign func setMatrix(M: CPointer<Unit>, row: UInt64, col: UInt64, v: Float32): Unit
|
||||
//foreign func getrowMatrix(M: CPointer<Unit>): UInt64
|
||||
//foreign func getcolMatrix(M: CPointer<Unit>): UInt64
|
||||
//foreign func sameShapeMatrix(M: CPointer<Unit>, S: CPointer<Unit>): Bool
|
||||
//foreign func dotShapeMatrix(M: CPointer<Unit>, S: CPointer<Unit>): Bool
|
||||
//foreign func plusMatrix(M: CPointer<Unit>, S:CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func plusBroadcastMatrix(M: CPointer<Unit>, T: Float32): CPointer<Unit>
|
||||
//foreign func subMatrix(M: CPointer<Unit>, S: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func subBroadcastMatrix(M: CPointer<Unit>, T: Float32): CPointer<Unit>
|
||||
//foreign func multiMatrix(M: CPointer<Unit>, S: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func multiBroadcastMatrix(M: CPointer<Unit>, T: Float32): CPointer<Unit>
|
||||
//foreign func divMatrix(M: CPointer<Unit>, S: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func divBroadcastMatrix(M: CPointer<Unit>, T: Float32): CPointer<Unit>
|
||||
//foreign func dotMatrix(M: CPointer<Unit>, S: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func transposeMatrix(M: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func xavierNormalInit(fan_in: UInt64, fan_out: UInt64): CPointer<Unit>
|
||||
//foreign func xavierUniformInit(fan_in: UInt64, fan_out: UInt64): CPointer<Unit>
|
||||
|
||||
|
||||
/* -------- 32位浮点数 -------- */
|
||||
public struct FP32 {
|
||||
private var val: Float32
|
||||
public init(t: Float32) { /* 以Float32初始化对象 */
|
||||
val = t }
|
||||
public func getValue() { /* 获取值 */
|
||||
return val }
|
||||
/* -------- 运算符重载 -------- */
|
||||
//public operator func =(t: Float32) { /*-------- this can not --------*/
|
||||
// val = t }
|
||||
public operator func +(t: Float32) {
|
||||
return FP32(val + t)}
|
||||
public operator func +(S: FP32) {
|
||||
return this + S.getValue() }
|
||||
public operator func -(t: Float32) {
|
||||
return FP32(val - t) }
|
||||
public operator func -(S: FP32) {
|
||||
return this - S.getValue() }
|
||||
public operator func *(t: Float32) {
|
||||
return FP32(val * t) }
|
||||
public operator func *(S: FP32) {
|
||||
return this * S.getValue() }
|
||||
public operator func /(t: Float32) {
|
||||
return FP32(val / t) }
|
||||
public operator func /(S: FP32) {
|
||||
return this / S.getValue() }
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 新版的矩阵对象,仓颉原生实现
|
||||
* TODO(20260517-qing): 设计原生的多维数组类
|
||||
*
|
||||
|
||||
___col___
|
||||
/ \
|
||||
+---------+ \
|
||||
| + + + + | | row - 第一维度
|
||||
| + + + + | |
|
||||
+---------+ /
|
||||
|
||||
|
||||
* TODO(20260523-qing): 以某种方式统一矩阵式的字符串矩阵和这里的数值矩阵
|
||||
*/
|
||||
public class Matrix1 {
|
||||
|
||||
private var row: Int64 /* ---- 行数 ---- */
|
||||
private var col: Int64 /* ---- 列数 ---- */
|
||||
private var arr: Array<FP32> /* ---- 数据 ---- */
|
||||
|
||||
/* -------- 构造器,默认值为0 -------- */
|
||||
public Matrix1(row: Int64, col: Int64) {
|
||||
this.row = row
|
||||
this.col = col
|
||||
this.arr = Array<FP32>(row * col, {_ => FP32(0.0f32)})
|
||||
}
|
||||
|
||||
/* --------- 这几个方法是访问器 -------- */
|
||||
/* ------------------------------------- */
|
||||
/* -------- TODO(20260524-qing): 使用prop成员 -------- */
|
||||
|
||||
public func get(r: Int64, c: Int64) {
|
||||
return arr.get(r * this.col + c)
|
||||
}
|
||||
|
||||
public func set(r: Int64, c: Int64, v: FP32) {
|
||||
arr[r * this.col + c] = v
|
||||
}
|
||||
|
||||
public func get_row() { return row }
|
||||
|
||||
public func get_col() { return col }
|
||||
|
||||
/* -------- 这两个函数是比较器 -------- */
|
||||
/* ------------------------------------ */
|
||||
public func has_same_shape(other: Matrix1) {
|
||||
let a = this.get_row() == other.get_row()
|
||||
let b = this.get_col() == other.get_col()
|
||||
return a && b
|
||||
}
|
||||
|
||||
public func has_dot_shape(other: Matrix1) {
|
||||
return this.get_col() == other.get_row()
|
||||
}
|
||||
|
||||
/* -------- 这些是运算符重载 -------- */
|
||||
/* ---------------------------------- */
|
||||
public operator func +(a: FP32) { /* ---- 矩阵广播加法 ---- */
|
||||
let r =get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v + a)
|
||||
}
|
||||
}
|
||||
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func +(S: Matrix1) { /* ---- 矩阵加法 ---- */
|
||||
if (!has_same_shape(S)) {
|
||||
throw IllegalArgumentException("Matrixes doesn't match")
|
||||
}
|
||||
let r =get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
let vs = S.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v + vs)
|
||||
}
|
||||
}
|
||||
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func -(a: FP32) { /* ---- 矩阵广播减法 ----*/
|
||||
let r = get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c){
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v - a)
|
||||
}
|
||||
}
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func -(S: Matrix1) { /* ---- 矩阵减法 ---- */
|
||||
if (!has_same_shape(S)) {
|
||||
throw IllegalArgumentException("Matrixes doesn't match")
|
||||
}
|
||||
let r =get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
let vs = S.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v - vs)
|
||||
}
|
||||
}
|
||||
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func *(a: FP32) { /* ---- 矩阵广播乘法 ---- */
|
||||
let r = get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c){
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v * a)
|
||||
}
|
||||
}
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func *(S: Matrix1) { /* ---- 逐元素相乘 ---- */
|
||||
if (!has_same_shape(S)) {
|
||||
throw IllegalArgumentException("Matrixes doesn't match")
|
||||
}
|
||||
let r =get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
let vs = S.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v * vs)
|
||||
}
|
||||
}
|
||||
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func /(a: FP32) { /* ---- 矩阵广播除法 ---- */
|
||||
let r = get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c){
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v / a)
|
||||
}
|
||||
}
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func /(S: Matrix1) { /* ---- 逐元素除法 ---- */
|
||||
if (!has_same_shape(S)) {
|
||||
throw IllegalArgumentException("Matrixes doesn't match")
|
||||
}
|
||||
let r =get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
let vs = S.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v / vs)
|
||||
}
|
||||
}
|
||||
|
||||
return M
|
||||
}
|
||||
|
||||
/* ------------------------------ */
|
||||
/* -------- 矩阵点积乘法 -------- */
|
||||
public func dot(S: Matrix1) {
|
||||
if (!has_dot_shape(S)) {
|
||||
throw IllegalArgumentException("Matrixes doesn't match")
|
||||
}
|
||||
let r =get_row()
|
||||
let c = S.get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..get_row()) {
|
||||
for (ic in 0..S.get_col()) {
|
||||
var sum = FP32(0.0f32)
|
||||
for (ik in 0..get_col()) {
|
||||
let a = get(ir, ik).getOrThrow()
|
||||
let b = S.get(ik, ic).getOrThrow()
|
||||
sum += a * b
|
||||
}
|
||||
M.set(ir, ic, sum)
|
||||
}
|
||||
}
|
||||
return M
|
||||
}
|
||||
|
||||
/* -------- 克隆一个一模一样的矩阵 -------- */
|
||||
public func clone() {
|
||||
let r = get_row()
|
||||
let c = get_col()
|
||||
let N = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = get(ir, ic).getOrThrow()
|
||||
N.set(ir, ic, v)
|
||||
}
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 矩阵转置,返回转置后的矩阵 -------- */
|
||||
public func T() {
|
||||
let r = get_row()
|
||||
let c = get_col()
|
||||
let N = Matrix1(c, r)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = get(ir, ic).getOrThrow()
|
||||
N.set(ic, ir, v)
|
||||
}
|
||||
}
|
||||
return N
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -------- 旧版的矩阵类,依赖c++编译的共享动态库 --------
|
||||
* TODO: 将矩阵元数据存储在仓颉部分(done at 202605xx ) */
|
||||
//public class Matrix {
|
||||
//
|
||||
// /* -------- 主初始化器,初始化并获取资源 -------- */
|
||||
// public init(row: UInt64, col: UInt64) {
|
||||
// unsafe { M = createMatrix(row, col) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 初始化器,利用已经存在的元数据进行构建 -------- */
|
||||
// public Matrix(m: CPointer<Unit>) { M = m }
|
||||
//
|
||||
// /* -------- Xavier正态分布初始化 -------- */
|
||||
// public static func initWithXavierNormal(fan_in: UInt64, fan_out: UInt64) {
|
||||
// unsafe {
|
||||
// let m = xavierNormalInit(fan_in, fan_out);
|
||||
// return Matrix(m)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* -------- Glorot均匀初始化 -------- */
|
||||
// public static func initWithXavierUniform(fan_in: UInt64, fan_out: UInt64) {
|
||||
// unsafe {
|
||||
// let m = xavierUniformInit(fan_in, fan_out);
|
||||
// return Matrix(m)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* ======== 在析构函数中释放资源 ======== */
|
||||
// ~init() {
|
||||
// unsafe { freeMatrix(M) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 取下标,由于不能返回值类型的引用被弃用 -------- */
|
||||
// //public operator func [](r: UInt64, c: UInt64): CPointer<Float32> {
|
||||
// // unsafe { return witchMatrix(M, r, c) }
|
||||
// //}
|
||||
//
|
||||
// /* -------- 获得某个矩阵单元的值 -------- */
|
||||
// public func get(r: UInt64, c: UInt64): Float32 {
|
||||
// unsafe { return getMatrix(M, r, c) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 设置某个矩阵单元的值 --------*/
|
||||
// public func set(r: UInt64, c: UInt64, v: Float32) {
|
||||
// unsafe { setMatrix(M, r, c, v) }
|
||||
// }
|
||||
//
|
||||
// /* ------- 两个矩阵形状是否相同 --------
|
||||
// * FIXME: 这个函数所重载的运算符不贴切 */
|
||||
// public operator func ==(s: Matrix): Bool {
|
||||
// unsafe { return sameShapeMatrix(M, s.getData()) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 获得行数 -------- */
|
||||
// public func getRow(): UInt64 {
|
||||
// unsafe { return getrowMatrix(M) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 获取列数 --------*/
|
||||
// public func getCol(): UInt64 {
|
||||
// unsafe { return getcolMatrix(M) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 获取指向元数据的C风格指针 -------- */
|
||||
// public func getData(): CPointer<Unit> { return M; }
|
||||
//
|
||||
// /* -------- 是否两个矩阵具有点积和的形状 -------- */
|
||||
// public func hasDotShapeWith(s: Matrix): Bool {
|
||||
// unsafe { return dotShapeMatrix(M, s.getData()) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 逐元素算数运算 -------- */
|
||||
// /* -------------------------------- */
|
||||
// public operator func +(s: Matrix): Matrix {
|
||||
// unsafe {
|
||||
// if (!sameShapeMatrix(M, s.getData())) {
|
||||
// println('------------')
|
||||
// print_shape()
|
||||
// s.print_shape()
|
||||
// println('------------')
|
||||
// throw IllegalArgumentException("not same shape")
|
||||
// }
|
||||
// let r = plusMatrix(M, s.getData())
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func +(t: Float32): Matrix {
|
||||
// unsafe {
|
||||
// let r = plusBroadcastMatrix(M, t)
|
||||
// return Matrix(r);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func -(s: Matrix): Matrix {
|
||||
// unsafe {
|
||||
// if (!(this == s)) {
|
||||
// println('------------')
|
||||
// print_shape()
|
||||
// s.print_shape()
|
||||
// println('------------')
|
||||
// throw IllegalArgumentException("not same shape")
|
||||
// }
|
||||
// let r = subMatrix(M, s.getData())
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func -(t: Float32): Matrix {
|
||||
// unsafe {
|
||||
// let r = subBroadcastMatrix(M, t)
|
||||
// return Matrix(r);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func *(s: Matrix): Matrix {
|
||||
// unsafe {
|
||||
// if (!(this == s)) {
|
||||
// println('------------')
|
||||
// print_shape()
|
||||
// s.print_shape()
|
||||
// println('------------')
|
||||
// throw IllegalArgumentException("not same shape")
|
||||
// }
|
||||
// let r = multiMatrix(M, s.getData())
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func *(t: Float32): Matrix {
|
||||
// unsafe {
|
||||
// let r = multiBroadcastMatrix(M, t)
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func /(s: Matrix): Matrix {
|
||||
// unsafe {
|
||||
// if (!(this == s)) {
|
||||
// println('------------')
|
||||
// print_shape()
|
||||
// s.print_shape()
|
||||
// println('------------')
|
||||
// throw IllegalArgumentException("not same shape")
|
||||
// }
|
||||
// let r = divMatrix(M, s.getData())
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func /(t: Float32): Matrix {
|
||||
// unsafe {
|
||||
// let r = divBroadcastMatrix(M, t)
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* -------------------------------- */
|
||||
// /* -------------------------------- */
|
||||
//
|
||||
//
|
||||
// /* -------- 矩阵点积和算法 -------- */
|
||||
// public func dot (s: Matrix): Matrix {
|
||||
// unsafe {
|
||||
// if (!hasDotShapeWith(s)) {
|
||||
// println('------------')
|
||||
// print_shape()
|
||||
// s.print_shape()
|
||||
// println('------------')
|
||||
// throw IllegalArgumentException("not dot shape")
|
||||
// }
|
||||
// let r = dotMatrix(M, s.getData())
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* -------- 矩阵转置 -------- */
|
||||
// public func T(): Matrix {
|
||||
// unsafe {
|
||||
// let r = transposeMatrix(M)
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* -------- 打印矩阵 -------- */
|
||||
// public func print(): Unit {
|
||||
// unsafe {
|
||||
// for (i in 0..getRow()) {
|
||||
// for (j in 0..getCol()) {
|
||||
// let a = this.get(i, j)
|
||||
// print(a)
|
||||
// print(" ")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// println()
|
||||
// }
|
||||
//
|
||||
// /* -------- 打印形状 -------- */
|
||||
// public func print_shape() {
|
||||
// println("${getRow()}, ${getCol()}")
|
||||
// }
|
||||
//
|
||||
// /* -------- 保存矩阵 -------- */
|
||||
// public func save(file: File) {
|
||||
// unsafe {
|
||||
// for (i in 0..getRow()) {
|
||||
// for (j in 0..getCol()) {
|
||||
// file.write(this.get(i, j).toString().toArray())
|
||||
// if (j < getCol()-1) {
|
||||
// file.write(" ".toArray()) }
|
||||
// else { file.write("\n".toArray()) }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* -------- 静态方法,加载单独项 -------- */
|
||||
// private static func loadItem(sr: StringReader<InputStream>) {
|
||||
// let s = sr.readUntil({r => r==r' ' || r ==r'\n'})
|
||||
// return Float32.parse(s.getOrThrow())
|
||||
// }
|
||||
//
|
||||
// /* -------- 静态方法,加载矩阵 -------- */
|
||||
// public static func load(sr: StringReader<InputStream>, row: UInt64, col: UInt64): Matrix {
|
||||
// let m = Matrix(row, col)
|
||||
// unsafe {
|
||||
// for (i in 0..m.getRow()) {
|
||||
// for (j in 0..m.getCol()) {
|
||||
// let f = loadItem(sr)
|
||||
// m.set(i, j, f)
|
||||
// }
|
||||
// }
|
||||
// m
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* -------- 矩阵的C风格指针 -------- */
|
||||
// private var M: CPointer<Unit>
|
||||
//}
|
||||
@@ -0,0 +1,56 @@
|
||||
package bot4cj.math
|
||||
import std.collection.*
|
||||
|
||||
/* -------- 张量 --------
|
||||
* INFO(20260524-qing): 输入一个维度列表 */
|
||||
class Tensor<T> {
|
||||
private let data: Array<T>
|
||||
private let dims: Array<Int64>
|
||||
private let size: Int64
|
||||
public init(dims: Array<Int64>, val: T) {
|
||||
var size = 1
|
||||
for (item in dims) { /* ---- 计算总量 ---- */
|
||||
size *= item
|
||||
}
|
||||
this.data = Array<T>(size, {_ => val})
|
||||
this.dims = dims
|
||||
this.size = size
|
||||
}
|
||||
|
||||
/* -------- 私有构造函数,用于内部创建张量 -------- */
|
||||
private init(dims: Array<Int64>, data: Array<T>) {
|
||||
this.dims = dims
|
||||
this.data = data
|
||||
var size = 1
|
||||
for (item in dims) {
|
||||
size *= item
|
||||
}
|
||||
this.size = size
|
||||
}
|
||||
|
||||
/* -------- 获取维度列表(形状) -------- */
|
||||
public func shape(): Array<Int64> {
|
||||
return dims
|
||||
}
|
||||
|
||||
/* -------- 逐元素进行某种运算 -------- */
|
||||
public func oneByOne(S: Tensor<T>, f: (T, T) -> T): Tensor<T> {
|
||||
let s1 = S.shape()
|
||||
|
||||
/* ---- 比较维度数量 ---- */
|
||||
if (s1 != dims) {
|
||||
throw IllegalArgumentException("Tensors' shape are not same")
|
||||
}
|
||||
|
||||
/* ---- 比较维度形状 ----*/
|
||||
for (i in 0..dims.size) {
|
||||
if (s1[i] != dims[i]) {
|
||||
throw IllegalArgumentException("Tensors' shape are not same")
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 逐个进行计算 ---- */
|
||||
let res_data = Array<T>(size, {i => f(data[i], S.data[i])})
|
||||
return Tensor<T>(dims, res_data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
// cnn.cj - 修正 forward 中的偏置加法
|
||||
package bot4cj.math
|
||||
|
||||
import std.math.*
|
||||
import std.random.*
|
||||
import std.collection.*
|
||||
|
||||
public class ConvLayer {
|
||||
|
||||
let nInputChannels: Int64
|
||||
let nOutputChannels: Int64
|
||||
let kernelHeight: Int64
|
||||
let kernelWidth: Int64
|
||||
let stride: Int64
|
||||
let padding: Int64
|
||||
let learningRate: FP32
|
||||
let f: Af
|
||||
|
||||
var connectingMatrix: Matrix1
|
||||
var kernels: Array<Matrix1>
|
||||
var biases: Matrix1
|
||||
|
||||
var prevInput: Option<Matrix1> = Option.None
|
||||
var prevInputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None
|
||||
var prevCombination: Option<Matrix1> = Option.None
|
||||
var prevOutput: Option<Matrix1> = Option.None
|
||||
var prevOutputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None
|
||||
|
||||
var kernelsGrad: Option<Array<Matrix1>> = Option.None
|
||||
var biasGrad: Option<Matrix1> = Option.None
|
||||
|
||||
public init(
|
||||
inCh: Int64,
|
||||
outCh: Int64,
|
||||
kernelSize: (Int64, Int64),
|
||||
stride: Int64,
|
||||
padding: Int64,
|
||||
lr: FP32,
|
||||
activation: Af,
|
||||
connMatrix: ?Matrix1
|
||||
) {
|
||||
nInputChannels = inCh
|
||||
nOutputChannels = outCh
|
||||
kernelHeight = kernelSize[0]
|
||||
kernelWidth = kernelSize[1]
|
||||
this.stride = stride
|
||||
this.padding = padding
|
||||
learningRate = lr
|
||||
this.f = activation
|
||||
|
||||
// 初始化 connectingMatrix
|
||||
connectingMatrix = match (connMatrix) {
|
||||
case m: Matrix1 => m
|
||||
case _ => Matrix1(nInputChannels, nOutputChannels)
|
||||
}
|
||||
let isNone = match (connMatrix) {
|
||||
case _: Matrix1 => false
|
||||
case _ => true
|
||||
}
|
||||
if (isNone) {
|
||||
for (r in 0..nInputChannels) {
|
||||
for (c in 0..nOutputChannels) {
|
||||
connectingMatrix.set(r, c, FP32(1.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化 kernels
|
||||
kernels = Array(nOutputChannels, { _ => Matrix1(1, 1) })
|
||||
for (oc in 0..nOutputChannels) {
|
||||
var nConnections = 0
|
||||
for (ic in 0..nInputChannels) {
|
||||
let val = connectingMatrix.get(ic, oc).getOrThrow()
|
||||
if (val.getValue() > 0.0) {
|
||||
nConnections += 1
|
||||
}
|
||||
}
|
||||
let kernelLen = nConnections * kernelHeight * kernelWidth
|
||||
let kernel = Matrix1(1, kernelLen)
|
||||
|
||||
Ini.xavierUniformInit(kernel, 1, kernelLen)
|
||||
|
||||
kernels[oc] = kernel
|
||||
}
|
||||
|
||||
// 初始化 biases
|
||||
biases = Matrix1(1, nOutputChannels)
|
||||
Ini.xavierUniformInit(biases, 1, nOutputChannels)
|
||||
}
|
||||
|
||||
// ==================== 前向传播 ====================
|
||||
public func forward(x: Matrix1, inputShape: (Int64, Int64, Int64, Int64)): Matrix1 {
|
||||
prevInput = Option.Some(x)
|
||||
prevInputShape = Option.Some(inputShape)
|
||||
|
||||
let batch = inputShape[0]
|
||||
let inH = inputShape[1]
|
||||
let inW = inputShape[2]
|
||||
|
||||
let outH = (inH + 2 * padding - kernelHeight) / stride + 1
|
||||
let outW = (inW + 2 * padding - kernelWidth) / stride + 1
|
||||
let outSize = batch * outH * outW
|
||||
|
||||
var combination = Matrix1(outSize, nOutputChannels)
|
||||
var output = Matrix1(outSize, nOutputChannels)
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (oh in 0..outH) {
|
||||
for (ow in 0..outW) {
|
||||
let outRow = b * outH * outW + oh * outW + ow
|
||||
|
||||
for (oc in 0..nOutputChannels) {
|
||||
var sum = FP32(0.0)
|
||||
var kernelIdx = 0
|
||||
|
||||
for (ic in 0..nInputChannels) {
|
||||
let connVal = connectingMatrix.get(ic, oc).getOrThrow()
|
||||
if (connVal.getValue() > 0.0) {
|
||||
for (kh in 0..kernelHeight) {
|
||||
for (kw in 0..kernelWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
let xVal = x.get(inRow, ic).getOrThrow()
|
||||
let kVal = kernels[oc].get(0, kernelIdx).getOrThrow()
|
||||
sum = sum + xVal * kVal
|
||||
}
|
||||
kernelIdx += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
combination.set(outRow, oc, sum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 手动广播偏置加法(因为 Matrix1 的 + 要求形状相同)
|
||||
for (r in 0..outSize) {
|
||||
for (c in 0..nOutputChannels) {
|
||||
let val = combination.get(r, c).getOrThrow()
|
||||
let biasVal = biases.get(0, c).getOrThrow()
|
||||
combination.set(r, c, val + biasVal)
|
||||
}
|
||||
}
|
||||
|
||||
prevCombination = Option.Some(combination)
|
||||
|
||||
output = f.forward(combination)
|
||||
prevOutput = Option.Some(output)
|
||||
prevOutputShape = Option.Some((batch, outH, outW, nOutputChannels))
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// ==================== 反向传播 ====================
|
||||
public func backward(errs: Matrix1): Matrix1 {
|
||||
var x = Matrix1(1, 1)
|
||||
match (prevInput) {
|
||||
case Some(T) => x = T
|
||||
case _ => throw Exception("No input data")
|
||||
}
|
||||
|
||||
var shape = (0, 0, 0, 0)
|
||||
match (prevInputShape) {
|
||||
case Some(T) => shape = T
|
||||
case _ => throw Exception("No input shape")
|
||||
}
|
||||
|
||||
var combination = Matrix1(1, 1)
|
||||
match (prevCombination) {
|
||||
case Some(val) => combination = val
|
||||
case _ => throw Exception("No combination")
|
||||
}
|
||||
|
||||
var outShape = (0, 0, 0, 0)
|
||||
match (prevOutputShape) {
|
||||
case Some(val) => outShape = val
|
||||
case _ => throw Exception("No output shape")
|
||||
}
|
||||
|
||||
let batch = shape[0]
|
||||
let inH = shape[1]
|
||||
let inW = shape[2]
|
||||
let outH = outShape[1]
|
||||
let outW = outShape[2]
|
||||
|
||||
let delta = errs * f.backward(combination)
|
||||
|
||||
calcKernelGradient(x, delta, batch, inH, inW, outH, outW)
|
||||
calcBiasGradient(delta)
|
||||
|
||||
let errsUp = calcUpsampledError(delta, batch, inH, inW, outH, outW)
|
||||
|
||||
return errsUp
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算卷积核梯度
|
||||
*/
|
||||
private func calcKernelGradient(
|
||||
x: Matrix1,
|
||||
delta: Matrix1,
|
||||
batch: Int64,
|
||||
inH: Int64,
|
||||
inW: Int64,
|
||||
outH: Int64,
|
||||
outW: Int64
|
||||
): Unit {
|
||||
var grads = Array(nOutputChannels, { _ => Matrix1(1, 1) })
|
||||
|
||||
for (oc in 0..nOutputChannels) {
|
||||
let kernelCols = kernels[oc].get_col()
|
||||
var grad = Matrix1(1, kernelCols)
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (oh in 0..outH) {
|
||||
for (ow in 0..outW) {
|
||||
let deltaRow = b * outH * outW + oh * outW + ow
|
||||
let deltaVal = delta.get(deltaRow, oc).getOrThrow()
|
||||
|
||||
var kernelIdx = 0
|
||||
for (ic in 0..nInputChannels) {
|
||||
let connVal = connectingMatrix.get(ic, oc).getOrThrow()
|
||||
if (connVal.getValue() > 0.0) {
|
||||
for (kh in 0..kernelHeight) {
|
||||
for (kw in 0..kernelWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
let xVal = x.get(inRow, ic).getOrThrow()
|
||||
let oldGrad = grad.get(0, kernelIdx).getOrThrow()
|
||||
grad.set(0, kernelIdx, oldGrad + xVal * deltaVal)
|
||||
}
|
||||
kernelIdx += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grads[oc] = grad
|
||||
}
|
||||
|
||||
kernelsGrad = Option.Some(grads)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算偏置梯度
|
||||
*/
|
||||
private func calcBiasGradient(delta: Matrix1): Unit {
|
||||
let rows = delta.get_row()
|
||||
var grad = Matrix1(1, nOutputChannels)
|
||||
|
||||
for (oc in 0..nOutputChannels) {
|
||||
var sum = FP32(0.0)
|
||||
for (r in 0..rows) {
|
||||
sum = sum + delta.get(r, oc).getOrThrow()
|
||||
}
|
||||
grad.set(0, oc, sum)
|
||||
}
|
||||
|
||||
biasGrad = Option.Some(grad)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算传递到上一层的误差
|
||||
*/
|
||||
private func calcUpsampledError(
|
||||
delta: Matrix1,
|
||||
batch: Int64,
|
||||
inH: Int64,
|
||||
inW: Int64,
|
||||
outH: Int64,
|
||||
outW: Int64
|
||||
): Matrix1 {
|
||||
let inSize = batch * inH * inW
|
||||
var errsUp = Matrix1(inSize, nInputChannels)
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (oh in 0..outH) {
|
||||
for (ow in 0..outW) {
|
||||
let deltaRow = b * outH * outW + oh * outW + ow
|
||||
|
||||
for (oc in 0..nOutputChannels) {
|
||||
let deltaVal = delta.get(deltaRow, oc).getOrThrow()
|
||||
|
||||
var kernelIdx = 0
|
||||
for (ic in 0..nInputChannels) {
|
||||
let connVal = connectingMatrix.get(ic, oc).getOrThrow()
|
||||
if (connVal.getValue() > 0.0) {
|
||||
for (kh in 0..kernelHeight) {
|
||||
for (kw in 0..kernelWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
let kVal = kernels[oc].get(0, kernelIdx).getOrThrow()
|
||||
let oldErr = errsUp.get(inRow, ic).getOrThrow()
|
||||
errsUp.set(inRow, ic, oldErr + deltaVal * kVal)
|
||||
}
|
||||
kernelIdx += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errsUp
|
||||
}
|
||||
|
||||
// ==================== 参数更新 ====================
|
||||
public func update(discount: Float32): Unit {
|
||||
var kGrad = Array(0, { _ => Matrix1(1, 1) })
|
||||
match (kernelsGrad) {
|
||||
case Some(val) => kGrad = val
|
||||
case _ => throw Exception("No kernel gradients")
|
||||
}
|
||||
|
||||
var bGrad = Matrix1(1, 1)
|
||||
match (biasGrad) {
|
||||
case Some(val) => bGrad = val
|
||||
case _ => throw Exception("No bias gradient")
|
||||
}
|
||||
|
||||
for (oc in 0..nOutputChannels) {
|
||||
let rows = kernels[oc].get_row()
|
||||
let cols = kernels[oc].get_col()
|
||||
for (r in 0..rows) {
|
||||
for (c in 0..cols) {
|
||||
let oldVal = kernels[oc].get(r, c).getOrThrow()
|
||||
let gradVal = kGrad[oc].get(r, c).getOrThrow()
|
||||
kernels[oc].set(r, c, oldVal - gradVal * learningRate * FP32(discount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (oc in 0..nOutputChannels) {
|
||||
let oldBias = biases.get(0, oc).getOrThrow()
|
||||
let gradBias = bGrad.get(0, oc).getOrThrow()
|
||||
biases.set(0, oc, oldBias - gradBias * learningRate * FP32(discount))
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 形状访问器 ====================
|
||||
public func getInShape(): (Int64, Int64) {
|
||||
(nInputChannels, kernelHeight * kernelWidth)
|
||||
}
|
||||
|
||||
public func getOutShape(): Int64 {
|
||||
nOutputChannels
|
||||
}
|
||||
|
||||
public func getLR(): FP32 {
|
||||
learningRate
|
||||
}
|
||||
|
||||
public func getFType(): String {
|
||||
f.get_type()
|
||||
}
|
||||
|
||||
public func calculateOutputSize(inputHeight: Int64, inputWidth: Int64): (Int64, Int64) {
|
||||
let outH = (inputHeight + 2 * padding - kernelHeight) / stride + 1
|
||||
let outW = (inputWidth + 2 * padding - kernelWidth) / stride + 1
|
||||
(outH, outW)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package bot4cj.math
|
||||
//foreign func start_fft(M: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func gen_wave(): CPointer<Unit>
|
||||
/*-------- 生成测试用的正弦波 --------*/
|
||||
//public func genWave(): Matrix {
|
||||
// unsafe { return Matrix(gen_wave()) }
|
||||
//}
|
||||
/*-------- 执行fft算法 --------*/
|
||||
//public func start_fft(M: Matrix): Matrix {
|
||||
// unsafe { return Matrix(start_fft(M.getData())) }
|
||||
//}
|
||||
@@ -0,0 +1,136 @@
|
||||
// flatten.cj - 移除调试代码
|
||||
package bot4cj.math
|
||||
|
||||
import std.math.*
|
||||
import std.collection.*
|
||||
|
||||
public class FlattenLayer {
|
||||
|
||||
let nInputChannels: Int64
|
||||
let inputHeight: Int64
|
||||
let inputWidth: Int64
|
||||
let outputSize: Int64
|
||||
|
||||
var prevInput: Option<Matrix1>
|
||||
var prevInputShape: Option<(Int64, Int64, Int64, Int64)>
|
||||
var prevOutput: Option<Matrix1>
|
||||
var prevOutputShape: Option<(Int64, Int64)>
|
||||
|
||||
public init(inputChannels: Int64, inputHeight: Int64, inputWidth: Int64) {
|
||||
nInputChannels = inputChannels
|
||||
this.inputHeight = inputHeight
|
||||
this.inputWidth = inputWidth
|
||||
outputSize = inputChannels * inputHeight * inputWidth
|
||||
|
||||
prevInput = None
|
||||
prevInputShape = None
|
||||
prevOutput = None
|
||||
prevOutputShape = None
|
||||
}
|
||||
|
||||
public init(inputShape: (Int64, Int64, Int64)) {
|
||||
nInputChannels = inputShape[0]
|
||||
inputHeight = inputShape[1]
|
||||
inputWidth = inputShape[2]
|
||||
outputSize = nInputChannels * inputHeight * inputWidth
|
||||
|
||||
prevInput = None
|
||||
prevInputShape = None
|
||||
prevOutput = None
|
||||
prevOutputShape = None
|
||||
}
|
||||
|
||||
public func forward(x: Matrix1, inputShape: (Int64, Int64, Int64, Int64)): Matrix1 {
|
||||
prevInput = x
|
||||
prevInputShape = inputShape
|
||||
|
||||
let batch = inputShape[0]
|
||||
let inH = inputShape[1]
|
||||
let inW = inputShape[2]
|
||||
let inCh = inputShape[3]
|
||||
|
||||
let totalRows = batch * outputSize
|
||||
var output = Matrix1(totalRows, 1)
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (h in 0..inH) {
|
||||
for (w in 0..inW) {
|
||||
for (ch in 0..inCh) {
|
||||
let inRow = b * inH * inW + h * inW + w
|
||||
let outRow = b * outputSize + (ch * inH * inW + h * inW + w)
|
||||
if (outRow < totalRows) {
|
||||
let val = x.get(inRow, ch).getOrThrow()
|
||||
output.set(outRow, 0, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prevOutput = output
|
||||
prevOutputShape = (totalRows, 1)
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
public func backward(errs: Matrix1): Matrix1 {
|
||||
var shape = (0, 0, 0, 0)
|
||||
match (prevInputShape) {
|
||||
case Some(T) => shape = T
|
||||
case _ => throw Exception("FlattenLayer.backward: No input shape. Call forward() first.")
|
||||
}
|
||||
|
||||
let batch = shape[0]
|
||||
let inH = shape[1]
|
||||
let inW = shape[2]
|
||||
let inCh = shape[3]
|
||||
|
||||
let inSize = batch * inH * inW
|
||||
var errsUp = Matrix1(inSize, nInputChannels)
|
||||
|
||||
let errsCols = errs.get_col()
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (h in 0..inH) {
|
||||
for (w in 0..inW) {
|
||||
let inRow = b * inH * inW + h * inW + w
|
||||
|
||||
for (ch in 0..inCh) {
|
||||
let outRow = b * outputSize + (ch * inH * inW + h * inW + w)
|
||||
|
||||
if (outRow < errs.get_row()) {
|
||||
if (errsCols == 1) {
|
||||
let val = errs.get(outRow, 0).getOrThrow()
|
||||
errsUp.set(inRow, ch, val)
|
||||
} else {
|
||||
var sum = FP32(0.0)
|
||||
for (col in 0..errsCols) {
|
||||
sum = sum + errs.get(outRow, col).getOrThrow()
|
||||
}
|
||||
errsUp.set(inRow, ch, sum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errsUp
|
||||
}
|
||||
|
||||
public func update(discount: Float32): Unit {
|
||||
// 无参数
|
||||
}
|
||||
|
||||
public func getInShape(): (Int64, Int64, Int64) {
|
||||
(nInputChannels, inputHeight, inputWidth)
|
||||
}
|
||||
|
||||
public func getOutShape(): Int64 {
|
||||
outputSize
|
||||
}
|
||||
|
||||
public func getOutputSize(): Int64 {
|
||||
outputSize
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package bot4cj.math
|
||||
|
||||
/**
|
||||
* 一维卡尔曼滤波器
|
||||
* 使用时先定义一个kalman指针,然后调用kalmanCreate()创建一个滤波器
|
||||
* 每次读取到传感器数据后即可调用KalmanFilter()来对数据进行滤波
|
||||
* 使用示例
|
||||
* extKalman_t p; //定义一个卡尔曼滤波器结构体
|
||||
* float SersorData; //需要进行滤波的数据
|
||||
* KalmanCreate(&p,20,200); //初始化该滤波器的Q=20 R=200参数
|
||||
* while(1)
|
||||
* {
|
||||
* SersorData = sersor(); //获取数据
|
||||
* SersorData = KalmanFilter(&p,SersorData); //对数据进行滤波
|
||||
* }
|
||||
*/
|
||||
@C
|
||||
public struct extKalman {
|
||||
var X_last: Float32 // 上一时刻的最优结果 X(k-|k-1)
|
||||
var P_last: Float32 // 上一时刻最优结果的协方差 P(k-1|k-1)
|
||||
var A: Float32 // 系统参数
|
||||
var B: Float32
|
||||
var Q: Float32
|
||||
var R: Float32
|
||||
var H: Float32
|
||||
|
||||
/**
|
||||
* @name kalmanCreate
|
||||
* @brief 创建一个卡尔曼滤波器
|
||||
* @param p: 滤波器
|
||||
* T_Q:系统噪声协方差
|
||||
* T_R:测量噪声协方差
|
||||
*
|
||||
* @retval none
|
||||
* @attention R固定,Q越大,代表越信任侧量值,Q无穷代表只用测量值
|
||||
* 反之,Q越小代表越信任模型预测值,Q为零则是只用模型预测
|
||||
*/
|
||||
public init(T_Q: Float32, T_R: Float32) {
|
||||
X_last = 0.0f32
|
||||
P_last = 0.0f32
|
||||
Q = T_Q
|
||||
R = T_R
|
||||
A = 1.0f32
|
||||
B = 0.0f32
|
||||
H = 1.0f32
|
||||
}
|
||||
|
||||
/**
|
||||
* @name KalmanFilter
|
||||
* @brief 卡尔曼滤波器
|
||||
* @param p: 滤波器
|
||||
* dat:待滤波数据
|
||||
* @retval 滤波后的数据
|
||||
* @attention Z(k)是系统输入,即测量值 X(k|k)是卡尔曼滤波后的值,即最终输出
|
||||
* A=1 B=0 H=1 I=1 W(K) V(k)是高斯白噪声,叠加在测量值上了,可以不用管
|
||||
* 以下是卡尔曼的5个核心公式
|
||||
* 一阶H'即为它本身,否则为转置矩阵
|
||||
*/
|
||||
public mut func filter(dat: Float32): Float32 {
|
||||
let X_mid = A * X_last //百度对应公式(1) x(k|k-1) = A*X(k-1|k-1)+B*U(k)+W(K)
|
||||
let P_mid = A * P_last + Q //百度对应公式(2) p(k|k-1) = A*p(k-1|k-1)*A'+Q
|
||||
let kg = P_mid / (P_mid + R) //百度对应公式(4) kg(k) = p(k|k-1)*H'/(H*p(k|k-1)*H'+R)
|
||||
let X_now = X_mid + kg * (dat - X_mid) //百度对应公式(3) x(k|k) = X(k|k-1)+kg(k)*(Z(k)-H*X(k|k-1))
|
||||
let P_now = (1.0f32 - kg) * P_mid //百度对应公式(5) p(k|k) = (I-kg(k)*H)*P(k|k-1)
|
||||
P_last = P_now
|
||||
X_last = X_now
|
||||
return X_now //输出预测结果x(k|k)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
package bot4cj.math
|
||||
import std.fs.*
|
||||
import std.io.*
|
||||
import std.convert.*
|
||||
//foreign func Sigmoid_do(m: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func Sigmoid_dot(m: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func ReLU_do(m: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func ReLU_dot(m: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func Leaky_ReLU_do(m: CPointer<Unit>, alpha: Float32): CPointer<Unit>
|
||||
//foreign func Leaky_ReLU_dot(m: CPointer<Unit>, alpha: Float32): CPointer<Unit>
|
||||
//foreign func Tanh_do(m: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func Tanh_dot(m: CPointer<Unit>): CPointer<Unit>
|
||||
|
||||
|
||||
/* -------- 新版的神经网络,使用原生矩阵对象实现 --------
|
||||
* TODO(20260517-qing): 增加batch批次处理,使用ArrayList<Matrix1>
|
||||
* TODO*(20260519-qing): 将神经网络转换为MatrixObject矩阵式对象
|
||||
* TODO(20260524-qing): 增加Adam优化器 */
|
||||
|
||||
public class Nn1 {
|
||||
|
||||
let inputno: Int64 /* -------- 输入 -------- */
|
||||
let outputno: Int64 /* -------- 输出 -------- */
|
||||
var weights: Matrix1 /* -- (inputs, outputs) - */
|
||||
var bias: Matrix1 /* ----- (1, output) ---- */
|
||||
let learning_rate: FP32 /* ------- 学习率 ------- */
|
||||
let f: Af /* ------ 激活函数 ------ */
|
||||
|
||||
/*------------------*/
|
||||
var prev_inputs: Option<Matrix1> = None /* ---- 之前的输入 ---- */
|
||||
var prev_raw_inputs: Option<Matrix1> = None /* ---- 之前的原始输出 ---- */
|
||||
var prev_outputs: Option<Matrix1> = None /* ---- 之前的输出 ---- */
|
||||
/*------------------*/
|
||||
var weights_grad: Option<Matrix1> = None /* ---- 权重梯度 ---- */
|
||||
var bias_grad: Option<Matrix1> = None /* ---- 偏置梯度 ---- */
|
||||
|
||||
|
||||
/* -------- 初始化器 --------
|
||||
* TODO(20260516-qing): 不要把初始化方法写死,使用枚举类 */
|
||||
public init(i: Int64, o: Int64, lr: Float32, f: Af) {
|
||||
inputno = i
|
||||
outputno =o
|
||||
learning_rate=FP32(lr)
|
||||
this.f = f
|
||||
weights = Matrix1(inputno, outputno)
|
||||
bias = Matrix1(1, outputno)
|
||||
|
||||
/* -------- 初始化权重 -------- */
|
||||
Ini.xavierUniformInit(weights, inputno, outputno)
|
||||
}
|
||||
|
||||
/* -------- 神经网络推理 -------- */
|
||||
public func forward(x: Matrix1) {
|
||||
prev_inputs = x
|
||||
var r = x.dot(weights)
|
||||
r = r + bias
|
||||
prev_raw_inputs = r
|
||||
let r1 = f.forward(r)
|
||||
prev_outputs = r1
|
||||
return r1
|
||||
}
|
||||
|
||||
/* -------- 反向传播算法 -------- */
|
||||
public func backward(errs: Matrix1)
|
||||
{
|
||||
if (is_softmax(f)) {
|
||||
return softmax_backward(errs)
|
||||
}
|
||||
let fbak = f.backward(prev_raw_inputs.getOrThrow())
|
||||
let first = errs * fbak /* 逐元素相乘 */
|
||||
let mx = prev_inputs.getOrThrow().T()
|
||||
weights_grad = mx.dot(first)
|
||||
bias_grad = first
|
||||
let errs_up = first.dot(weights.T())
|
||||
return errs_up
|
||||
}
|
||||
|
||||
/* -------- 更新权重值、偏爱因子 --------
|
||||
* NOTE: discount(折扣)是为了方便深度强化学习给出奖励值 */
|
||||
public func update(discount: Float32) {
|
||||
weights = weights - weights_grad.getOrThrow() * learning_rate * FP32(discount)
|
||||
bias = bias - bias_grad.getOrThrow() * learning_rate * FP32(discount)
|
||||
}
|
||||
|
||||
/**/
|
||||
private static func is_softmax(f: Af) {
|
||||
return match (f) {
|
||||
case Af.Softmax => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
private func softmax_backward(errs: Matrix1) {
|
||||
/* 使用雅可比矩阵的方式 */
|
||||
let y = prev_outputs.getOrThrow() /* softmax 输出 */
|
||||
let dy = errs /* 上游梯度 ∂L/∂y */
|
||||
let jacobian = f.backward(y) /* n×n 雅可比矩阵 */
|
||||
|
||||
/* 将 dy 展平成列向量 n×1 */
|
||||
let row = dy.get_row()
|
||||
let col = dy.get_col()
|
||||
let n = row * col
|
||||
var dy_vec = Matrix1(n, 1)
|
||||
var idx = 0
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let val = dy.get(r, c).getOrThrow()
|
||||
dy_vec.set(idx, 0, val)
|
||||
idx = idx + 1
|
||||
}
|
||||
}
|
||||
|
||||
/* dx_vec = J · dy_vec (n×n × n×1 = n×1) */
|
||||
var dx_vec = Matrix1(n, 1)
|
||||
for (i in 0..n) {
|
||||
var sum = 0.0f32
|
||||
for (j in 0..n) {
|
||||
let j_val = jacobian.get(i, j).getOrThrow().getValue()
|
||||
let dy_val = dy_vec.get(j, 0).getOrThrow().getValue()
|
||||
sum = sum + j_val * dy_val
|
||||
}
|
||||
dx_vec.set(i, 0, FP32(sum))
|
||||
}
|
||||
|
||||
/* 恢复为原始形状,作为 first */
|
||||
var first = Matrix1(row, col)
|
||||
idx = 0
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let val = dx_vec.get(idx, 0).getOrThrow()
|
||||
first.set(r, c, val)
|
||||
idx = idx + 1
|
||||
}
|
||||
}
|
||||
|
||||
let mx = prev_inputs.getOrThrow().T()
|
||||
weights_grad = mx.dot(first)
|
||||
bias_grad = first
|
||||
let errs_up = first.dot(weights.T())
|
||||
return errs_up
|
||||
}
|
||||
|
||||
|
||||
/* -------- 形状访问器 -------- */
|
||||
public func get_in_shape() {
|
||||
return inputno }
|
||||
|
||||
public func get_out_shape() {
|
||||
return outputno }
|
||||
|
||||
public func get_lr() {
|
||||
return learning_rate }
|
||||
|
||||
public func get_f_type() {
|
||||
return f.get_type() }
|
||||
|
||||
public func get_f_val() {
|
||||
return f.get_value() }
|
||||
/* ---------------------------- */
|
||||
|
||||
}
|
||||
|
||||
|
||||
///* -------- 旧版激活函数 --------*/
|
||||
//public enum ActivationFuncs {
|
||||
// Sigmoid | ReLU | Leaky_ReLU | Tanh
|
||||
// func fdo(m: Matrix): Matrix { // 暂时只设置ReLU
|
||||
// unsafe {
|
||||
// match(this) {
|
||||
// case Sigmoid => Matrix(Sigmoid_do(m.getData()))
|
||||
// case ReLU => Matrix(ReLU_do(m.getData()))
|
||||
// case Leaky_ReLU => Matrix(Leaky_ReLU_do(m.getData(), 0.1f32))
|
||||
// case Tanh => Matrix(Tanh_do(m.getData()))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// func fdot(m: Matrix): Matrix {
|
||||
// unsafe {
|
||||
// match(this) {
|
||||
// case Sigmoid => Matrix(Sigmoid_dot(m.getData()))
|
||||
// case ReLU => Matrix(ReLU_dot(m.getData()))
|
||||
// case Leaky_ReLU => Matrix(Leaky_ReLU_dot(m.getData(), 0.1f32))
|
||||
// case Tanh => Matrix(Tanh_dot(m.getData()))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// func toString(): String {
|
||||
// match(this) {
|
||||
// case Sigmoid => "Sigmoid"
|
||||
// case ReLU => "ReLU"
|
||||
// case Leaky_ReLU => "Leaky_ReLU"
|
||||
// case Tanh => "Tanh"
|
||||
// }
|
||||
// }
|
||||
// static func parse(s: String) {
|
||||
// match(s) {
|
||||
// case "Sigmoid" => ActivationFuncs.Sigmoid
|
||||
// case "ReLU" => ActivationFuncs.ReLU
|
||||
// case "Leaky_ReLU" => ActivationFuncs.Leaky_ReLU
|
||||
// case "Tanh" => ActivationFuncs.Tanh
|
||||
// case _ => throw IllegalArgumentException()
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
///* -------- 旧版的神经网络类 --------
|
||||
// * 这个实现依赖C++语言的动态分享库,特点是速度快
|
||||
// * 这个实现可能是有问题的 */
|
||||
//public class Nn {
|
||||
//
|
||||
// /* -------- 主初始化器 -------- */
|
||||
// public init(
|
||||
// inputno: UInt64, outputno: UInt64, f: ActivationFuncs, learning_rate: Float32) {
|
||||
// this.inputno = inputno
|
||||
// this.outputno = outputno
|
||||
// weights = Matrix.initWithXavierUniform(inputno, outputno)
|
||||
// bias = Matrix(1, outputno)
|
||||
// this.f = f
|
||||
// this.learning_rate = learning_rate
|
||||
// }
|
||||
//
|
||||
// /* -------- 提供了权重矩阵的初始化器 -------- */
|
||||
// public init(
|
||||
// inputno: UInt64, outputno: UInt64, f: ActivationFuncs, learning_rate: Float32, weights: Matrix, bias: Matrix) {
|
||||
// this.inputno = inputno
|
||||
// this.outputno = outputno
|
||||
// this.weights = weights
|
||||
// this.bias = bias
|
||||
// this.f = f
|
||||
// this.learning_rate = learning_rate
|
||||
// }
|
||||
//
|
||||
// /* -------- 神经网络推理 -------- */
|
||||
// func forward(x: Matrix): Matrix {
|
||||
// /* x => (1, inputs) */
|
||||
// let r = x.dot(weights) + bias /* (1, outputno) */
|
||||
// return r
|
||||
// }
|
||||
//
|
||||
// /* -------- 实施激活函数 -------- */
|
||||
// func fdo(x: Matrix): Matrix {
|
||||
// return f.fdo(x) }
|
||||
//
|
||||
// /* -------- 激活函数导数 -------- */
|
||||
// func fdot(x: Matrix): Matrix {
|
||||
// return f.fdot(x) }
|
||||
//
|
||||
// /* This is Wrong */
|
||||
// //func bak_bsgrad(prev_raw_outputs_dotf: Matrix, errs: Matrix): Matrix {
|
||||
// /* prev_raw_outputs_dotf => (1, outputno)
|
||||
// * errs => (1, outputno) */
|
||||
//
|
||||
// // return errs * prev_raw_outputs_dotf
|
||||
// //}
|
||||
//
|
||||
// /* -------- 权重梯度的传播 -------- */
|
||||
// func bak_wsgrad(prev_inputs: Matrix, delta: Matrix) {
|
||||
// /* prev_inputs => (1, inputs) */
|
||||
// return prev_inputs.T().dot(delta) /* (inputs, outputs) */
|
||||
// }
|
||||
//
|
||||
// /* -------- 计算上一层的误差 -------- */
|
||||
// func bak_uperrs(delta: Matrix, prev_dotf: Matrix) {
|
||||
// let first = weights.dot(delta.T()).T() // (1, inputs)
|
||||
//
|
||||
// var uperrs = first * prev_dotf
|
||||
// uperrs
|
||||
// }
|
||||
//
|
||||
// /* -------- 更新权重、偏爱因子 -------- */
|
||||
// func update(weights_grad: Matrix, bias_grad: Matrix, discount: Float32) {
|
||||
// weights -= weights_grad * learning_rate * discount
|
||||
// bias -= bias_grad * learning_rate * discount
|
||||
// }
|
||||
//
|
||||
// /* -------- 打印权重、偏爱因子 -------- */
|
||||
// func print() {
|
||||
// weights.print()
|
||||
// bias.print()
|
||||
// }
|
||||
//
|
||||
// /* -------- 打印神经网络全连接层的形状 -------- */
|
||||
// func print_shape() {
|
||||
// println("${inputno}, ${outputno}") }
|
||||
//
|
||||
// /* -------- 储存神经网络 -------- */
|
||||
// func save(file: File) {
|
||||
// file.write("FullConnectLayer ".toArray())
|
||||
// file.write(inputno.toString().toArray())
|
||||
// file.write(" ".toArray())
|
||||
// file.write(outputno.toString().toArray())
|
||||
// file.write(" ".toArray())
|
||||
// file.write(learning_rate.toString().toArray())
|
||||
// file.write(" ".toArray())
|
||||
// file.write(f.toString().toArray())
|
||||
// file.write("\n".toArray())
|
||||
// weights.save(file)
|
||||
// //file.write("\n".toArray())
|
||||
// bias.save(file)
|
||||
// //file.write("\n".toArray())
|
||||
// }
|
||||
//
|
||||
// /* -------- 静态方法,加载单个项目 -------- */
|
||||
// static func loadItem<T>(sr: StringReader<InputStream>) where T <: Parsable<T>
|
||||
// {
|
||||
// let s = sr.readUntil({r => r == r" " || r == r"\n"})
|
||||
// let ss = s.getOrThrow().trimAscii()
|
||||
// println(ss)
|
||||
// return T.parse(ss)
|
||||
// }
|
||||
//
|
||||
// /* -------- 静态方法,加载神经网络 -------- */
|
||||
// static func load(sr: StringReader<InputStream>)
|
||||
// {
|
||||
// let inputno = loadItem<UInt64>(sr)
|
||||
// let outputno = loadItem<UInt64>(sr)
|
||||
// let lr = loadItem<Float32>(sr)
|
||||
// let fna = sr.readUntil(r'\n')
|
||||
// let f = ActivationFuncs.parse(fna.getOrThrow().trimAscii())
|
||||
// let weights = Matrix.load(sr, inputno, outputno)
|
||||
// let bias = Matrix.load(sr, 1, outputno)
|
||||
// Nn(inputno, outputno, f, lr, weights, bias)
|
||||
// }
|
||||
//
|
||||
// let inputno: UInt64
|
||||
// let outputno: UInt64
|
||||
// var weights: Matrix /* (inputs, outputs) */
|
||||
// var bias: Matrix /* (1, outputs) */
|
||||
// var learning_rate: Float32
|
||||
// let f: ActivationFuncs
|
||||
//}
|
||||
@@ -0,0 +1,164 @@
|
||||
package bot4cj.math
|
||||
import std.io.*
|
||||
import std.fs.*
|
||||
import std.math.*
|
||||
import std.time.*
|
||||
import std.random.*
|
||||
import std.collection.*
|
||||
|
||||
/* -------- 准备与门的测试用例 --------
|
||||
* INFO(20260524-qing): 输入{2},输出{1} */
|
||||
public func prepare_andDoor(m: Matrix1, t: Matrix1) {
|
||||
let random = Random(UInt64(DateTime.now().nanosecond))
|
||||
let a = random.nextInt8(2)
|
||||
let b = random.nextInt8(2)
|
||||
let t0 = if ((a == 0 && b == 0) || (a ==1 && b == 1)) { 1.0f32 } else { 0.0f32 }
|
||||
//let t1 = if (a != b) { 1.0f32 } else { 0.0f32 }
|
||||
m.set(0, 0, FP32(Float32(a)))
|
||||
m.set(0, 1, FP32(Float32(b)))
|
||||
t.set(0, 0, FP32(t0))
|
||||
//t.set(0, 1, FP32(t1))
|
||||
}
|
||||
|
||||
/* -------- 新版神经网络推理,使用原生矩阵 -------- */
|
||||
public func forward1(nns: ArrayList<Nn1>, m: Matrix1) {
|
||||
var out_dof = Matrix1(1, 1) /* ---- 给一个值占位 ----*/
|
||||
for (idx in 0..nns.size) {
|
||||
let x = if (idx == 0) { m } else { out_dof }
|
||||
out_dof= nns[idx].forward(x)
|
||||
}
|
||||
return out_dof
|
||||
}
|
||||
|
||||
/* 【神经网络训练】
|
||||
* description: 输入一组原始数据和教师数据,进行神经网络训练
|
||||
* @param nns: 输入神经网络结构
|
||||
* @param m: 输入一组原始数据
|
||||
* @param t: 输入一组教师数据
|
||||
* @param discount: 输入本次训练的折扣系数
|
||||
*/
|
||||
public func train1(nns: ArrayList<Nn1>, m: Matrix1, t: Matrix1, discount: Float32) {
|
||||
|
||||
/* ======== 这个对象用于暂时占位 ======== */
|
||||
var res = Matrix1(1, 1)
|
||||
|
||||
/* ======== 正向传播 ======== */
|
||||
for (idx in 0..nns.size) {
|
||||
let x = if (idx == 0) { m } else { res }
|
||||
res = nns[idx].forward(x)
|
||||
}
|
||||
|
||||
/* ======== 计算误差 ======== */
|
||||
var errs = t - res
|
||||
|
||||
/* ======== 反向传播 ======== */
|
||||
for (idx in (nns.size-1)..=0:-1) {
|
||||
errs = nns[idx].backward(errs)
|
||||
}
|
||||
|
||||
/* ======== 梯度更新 ======== */
|
||||
for (idx in 0..nns.size) {
|
||||
nns[idx].update(discount)
|
||||
}
|
||||
|
||||
/* ======== 计算MSE损失 ========
|
||||
FIXME*(20260517-qing): 对于多个结果计算误差(目前只计算了第一个结果) */
|
||||
|
||||
let t0 = t.get(0, 0).getOrThrow().getValue()
|
||||
//let t1 = t.get(0, 1).getOrThrow().getValue()
|
||||
let r0 = res.get(0, 0).getOrThrow().getValue()
|
||||
//let r1 = res.get(0, 1).getOrThrow().getValue()
|
||||
let loss0 = 0.5 * (t0 - r0) * (t0 - r0)
|
||||
//let loss1 = 0.5 * (t1 - r1) * (t1 - r1)
|
||||
//return (loss0 + loss1) / 2.0
|
||||
return loss0
|
||||
}
|
||||
|
||||
/* -------- (旧版)加载多层神经网络 --------
|
||||
TODO: 也许用类封装
|
||||
INFO(20260517-qing): 新版使用仓颉原生矩阵,然后与MatrixObject类互转 */
|
||||
//public func load_nns(filename: String): ArrayList<Nn> {
|
||||
// let nns = ArrayList<Nn>()
|
||||
// let file = File(filename, OpenMode.Read)
|
||||
// let sr = StringReader<InputStream>(file)
|
||||
// while (true) {
|
||||
// if (let Some(S) <- sr.readUntil(r' ')) {
|
||||
// var S2 = S.trimAscii()
|
||||
// if (S2 == "FullConnectLayer") {
|
||||
// nns.add(Nn.load(sr)) }
|
||||
// else if (S2 == "") { break }
|
||||
// else { throw IllegalArgumentException() }
|
||||
// }
|
||||
// else { break }
|
||||
// }
|
||||
|
||||
// file.close()
|
||||
// return nns
|
||||
//}
|
||||
|
||||
/* -------- (旧版)储存多层神经网络 -------- */
|
||||
//public func save_nns(nns: ArrayList<Nn>, filename: String) {
|
||||
//
|
||||
// let file: File
|
||||
// if (exists(filename)) {
|
||||
// file = File(filename, OpenMode.Write) }
|
||||
// else { file= File.create(filename) }
|
||||
// for (nn in nns) { /* 存储全部的层 */
|
||||
// nn.save(file) }
|
||||
// file.close()
|
||||
//}
|
||||
|
||||
/* -------- 旧版神经网络推理 -------- */
|
||||
//public func forward(nns: ArrayList<Nn>, m: Matrix) {
|
||||
// var out_dof = Matrix(1, 1)
|
||||
// for (idx in 0..nns.size) {
|
||||
// let x = if (idx == 0) { m } else { out_dof }
|
||||
// let res = nns[idx].forward(x)
|
||||
// out_dof = nns[idx].fdo(res)
|
||||
// }
|
||||
// return out_dof
|
||||
//}
|
||||
|
||||
/* -------- 旧版的神经网络训练 -------- */
|
||||
//public func train(nns: ArrayList<Nn>, m: Matrix, t: Matrix, discount: Float32) {
|
||||
//
|
||||
// /* ----------------- */
|
||||
// let outs_dof = ArrayList<Matrix> ()
|
||||
// let outs_dotf = ArrayList<Matrix> ()
|
||||
//
|
||||
// /* -------- 正向传播 -------- */
|
||||
// for (idx in 0..nns.size) {
|
||||
// let x = if (idx == 0) { m } else { outs_dof[idx-1] }
|
||||
// let res = nns[idx].forward(x)
|
||||
// let out_dof = nns[idx].fdo(res)
|
||||
// let out_dotf = nns[idx].fdot(out_dof)
|
||||
// outs_dof.add(out_dof)
|
||||
// outs_dotf.add(out_dotf)
|
||||
// }
|
||||
//
|
||||
// /* -------- 计算误差 -------- */
|
||||
// var errs = outs_dof[nns.size-1] - t
|
||||
//
|
||||
// /* -------- 反向传播 -------- */
|
||||
// for (idx in (nns.size-1)..=0:-1) {
|
||||
// let up = if (idx == 0) { m} else { outs_dof[idx-1] }
|
||||
// let bias_grad = errs
|
||||
// //let delta = nns[idx].bak_bsgrad(errs, outs_dotf[idx])
|
||||
// let weights_grad = nns[idx].bak_wsgrad(up, bias_grad)
|
||||
// if (idx > 0) {
|
||||
// errs = nns[idx].bak_uperrs(bias_grad, outs_dotf[idx-1]) }
|
||||
// nns[idx].update(weights_grad, bias_grad, discount)
|
||||
// }
|
||||
//
|
||||
// /* -------- 输出损失 -------- */
|
||||
// unsafe {
|
||||
// let t0 = t.get(0, 0)
|
||||
// let t1 = t.get(0, 1)
|
||||
// let r0 = outs_dof[nns.size-1].get(0, 0)
|
||||
// let r1 = outs_dof[nns.size-1].get(0, 1)
|
||||
// let loss0 = -t0 * log(r0) - (1.0f32-t0) * log(1.0f32-r0);
|
||||
// let loss1 = -t1 * log(r1) - (1.0f32-t1) * log(1.0f32-r1);
|
||||
// return 0.5 * (loss0 * loss0 + loss1 * loss1)
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -0,0 +1,418 @@
|
||||
// pooling_layer.cj
|
||||
package bot4cj.math
|
||||
|
||||
import std.math.*
|
||||
import std.collection.*
|
||||
|
||||
/**
|
||||
* 池化层类
|
||||
* 设计风格与 Nn1 和 ConvLayer 保持一致
|
||||
*/
|
||||
public class PoolingLayer {
|
||||
|
||||
// ==================== 层配置参数 ====================
|
||||
let nChannels: Int64
|
||||
let poolHeight: Int64
|
||||
let poolWidth: Int64
|
||||
let stride: Int64
|
||||
let padding: Int64
|
||||
let poolType: String
|
||||
let f: Af
|
||||
let learningRateWeight: FP32
|
||||
let learningRateBias: FP32
|
||||
|
||||
// ==================== 可学习参数 ====================
|
||||
var weights: Matrix1
|
||||
var biases: Matrix1
|
||||
|
||||
// ==================== 运行时缓存 ====================
|
||||
var prevInput: Option<Matrix1> = Option.None
|
||||
var prevInputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None
|
||||
var prevCombination: Option<Matrix1> = Option.None
|
||||
var prevOutput: Option<Matrix1> = Option.None
|
||||
var prevOutputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None
|
||||
var prevMaxIndices: Option<Matrix1> = Option.None
|
||||
|
||||
var weightsGrad: Option<Matrix1> = Option.None
|
||||
var biasGrad: Option<Matrix1> = Option.None
|
||||
|
||||
// ==================== 构造函数 ====================
|
||||
public init(
|
||||
nChannels: Int64,
|
||||
poolSize: (Int64, Int64),
|
||||
stride: Int64,
|
||||
padding: Int64,
|
||||
poolType: String,
|
||||
activation: Af,
|
||||
lrWeight: FP32,
|
||||
lrBias: FP32
|
||||
) {
|
||||
this.nChannels = nChannels
|
||||
poolHeight = poolSize[0]
|
||||
poolWidth = poolSize[1]
|
||||
this.stride = stride
|
||||
this.padding = padding
|
||||
this.poolType = poolType
|
||||
this.f = activation
|
||||
learningRateWeight = lrWeight
|
||||
learningRateBias = lrBias
|
||||
|
||||
// 初始化权值
|
||||
weights = Matrix1(1, nChannels)
|
||||
if (poolType == "average") {
|
||||
let avgWeight = FP32(1.0) / FP32(Float32(poolHeight * poolWidth))
|
||||
for (c in 0..nChannels) {
|
||||
weights.set(0, c, avgWeight)
|
||||
}
|
||||
} else {
|
||||
for (c in 0..nChannels) {
|
||||
weights.set(0, c, FP32(1.0))
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化偏置为 0
|
||||
biases = Matrix1(1, nChannels)
|
||||
for (c in 0..nChannels) {
|
||||
biases.set(0, c, FP32(0.0))
|
||||
}
|
||||
}
|
||||
|
||||
public init(
|
||||
nChannels: Int64,
|
||||
poolSize: (Int64, Int64),
|
||||
poolType: String,
|
||||
activation: Af
|
||||
) {
|
||||
this.nChannels = nChannels
|
||||
poolHeight = poolSize[0]
|
||||
poolWidth = poolSize[1]
|
||||
stride = poolHeight
|
||||
padding = 0
|
||||
this.poolType = poolType
|
||||
this.f = activation
|
||||
learningRateWeight = FP32(0.1)
|
||||
learningRateBias = FP32(0.1)
|
||||
|
||||
weights = Matrix1(1, nChannels)
|
||||
if (poolType == "average") {
|
||||
let avgWeight = FP32(1.0) / FP32(Float32(poolHeight * poolWidth))
|
||||
for (c in 0..nChannels) {
|
||||
weights.set(0, c, avgWeight)
|
||||
}
|
||||
} else {
|
||||
for (c in 0..nChannels) {
|
||||
weights.set(0, c, FP32(1.0))
|
||||
}
|
||||
}
|
||||
|
||||
biases = Matrix1(1, nChannels)
|
||||
for (c in 0..nChannels) {
|
||||
biases.set(0, c, FP32(0.0))
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 前向传播 ====================
|
||||
public func forward(x: Matrix1, inputShape: (Int64, Int64, Int64, Int64)): Matrix1 {
|
||||
prevInput = Option.Some(x)
|
||||
prevInputShape = Option.Some(inputShape)
|
||||
|
||||
let batch = inputShape[0]
|
||||
let inH = inputShape[1]
|
||||
let inW = inputShape[2]
|
||||
|
||||
let outH = (inH + 2 * padding - poolHeight) / stride + 1
|
||||
let outW = (inW + 2 * padding - poolWidth) / stride + 1
|
||||
let outSize = batch * outH * outW
|
||||
|
||||
var combination = Matrix1(outSize, nChannels)
|
||||
var output = Matrix1(outSize, nChannels)
|
||||
var maxIndices = Matrix1(outSize, nChannels)
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (oh in 0..outH) {
|
||||
for (ow in 0..outW) {
|
||||
let outRow = b * outH * outW + oh * outW + ow
|
||||
|
||||
for (ch in 0..nChannels) {
|
||||
var pooledVal = FP32(0.0)
|
||||
var maxIdx = 0
|
||||
|
||||
if (poolType == "max") {
|
||||
var maxVal = FP32(-1.0e30)
|
||||
var kernelIdx = 0
|
||||
for (kh in 0..poolHeight) {
|
||||
for (kw in 0..poolWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
let val = x.get(inRow, ch).getOrThrow()
|
||||
if (val.getValue() > maxVal.getValue()) {
|
||||
maxVal = val
|
||||
maxIdx = kernelIdx
|
||||
}
|
||||
}
|
||||
kernelIdx += 1
|
||||
}
|
||||
}
|
||||
pooledVal = maxVal
|
||||
} else {
|
||||
var sum = FP32(0.0)
|
||||
var count = 0
|
||||
for (kh in 0..poolHeight) {
|
||||
for (kw in 0..poolWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
sum = sum + x.get(inRow, ch).getOrThrow()
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
pooledVal = sum / FP32(Float32(count))
|
||||
}
|
||||
}
|
||||
|
||||
combination.set(outRow, ch, pooledVal)
|
||||
maxIndices.set(outRow, ch, FP32(Float32(maxIdx)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 逐通道应用权值和偏置
|
||||
for (r in 0..outSize) {
|
||||
for (ch in 0..nChannels) {
|
||||
let val = combination.get(r, ch).getOrThrow()
|
||||
let w = weights.get(0, ch).getOrThrow()
|
||||
let b = biases.get(0, ch).getOrThrow()
|
||||
combination.set(r, ch, val * w + b)
|
||||
}
|
||||
}
|
||||
|
||||
prevCombination = Option.Some(combination)
|
||||
|
||||
output = f.forward(combination)
|
||||
prevOutput = Option.Some(output)
|
||||
prevOutputShape = Option.Some((batch, outH, outW, nChannels))
|
||||
prevMaxIndices = Option.Some(maxIndices)
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// ==================== 反向传播 ====================
|
||||
public func backward(errs: Matrix1): Matrix1 {
|
||||
var x = Matrix1(1, 1)
|
||||
match (prevInput) {
|
||||
case Some(val) => x = val
|
||||
case _ => throw Exception("No input data")
|
||||
}
|
||||
|
||||
var shape = (0, 0, 0, 0)
|
||||
match (prevInputShape) {
|
||||
case Some(val) => shape = val
|
||||
case _ => throw Exception("No input shape")
|
||||
}
|
||||
|
||||
var combination = Matrix1(1, 1)
|
||||
match (prevCombination) {
|
||||
case Some(val) => combination = val
|
||||
case _ => throw Exception("No combination")
|
||||
}
|
||||
|
||||
var outShape = (0, 0, 0, 0)
|
||||
match (prevOutputShape) {
|
||||
case Some(val) => outShape = val
|
||||
case _ => throw Exception("No output shape")
|
||||
}
|
||||
|
||||
var maxIndices = Matrix1(1, 1)
|
||||
let hasMaxIndices = match (prevMaxIndices) {
|
||||
case Some(_) => true
|
||||
case _ => false
|
||||
}
|
||||
if (hasMaxIndices) {
|
||||
maxIndices = match (prevMaxIndices) {
|
||||
case Some(val) => val
|
||||
case _ => Matrix1(1, 1)
|
||||
}
|
||||
}
|
||||
|
||||
let batch = shape[0]
|
||||
let inH = shape[1]
|
||||
let inW = shape[2]
|
||||
let outH = outShape[1]
|
||||
let outW = outShape[2]
|
||||
|
||||
let delta = errs * f.backward(combination)
|
||||
|
||||
calcWeightBiasGradient(delta, combination)
|
||||
|
||||
let errsUp = calcUpsampledError(delta, maxIndices, batch, inH, inW, outH, outW)
|
||||
|
||||
return errsUp
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算权值和偏置梯度
|
||||
*/
|
||||
private func calcWeightBiasGradient(delta: Matrix1, combination: Matrix1): Unit {
|
||||
let outSize = delta.get_row()
|
||||
|
||||
var wGrad = Matrix1(1, nChannels)
|
||||
var bGrad = Matrix1(1, nChannels)
|
||||
|
||||
for (ch in 0..nChannels) {
|
||||
var wSum = FP32(0.0)
|
||||
var bSum = FP32(0.0)
|
||||
for (r in 0..outSize) {
|
||||
let d = delta.get(r, ch).getOrThrow()
|
||||
bSum = bSum + d
|
||||
let comb = combination.get(r, ch).getOrThrow()
|
||||
wSum = wSum + d * comb
|
||||
}
|
||||
wGrad.set(0, ch, wSum)
|
||||
bGrad.set(0, ch, bSum)
|
||||
}
|
||||
|
||||
weightsGrad = Option.Some(wGrad)
|
||||
biasGrad = Option.Some(bGrad)
|
||||
}
|
||||
|
||||
/**
|
||||
* 上采样误差(池化层的反向传播核心)
|
||||
*/
|
||||
private func calcUpsampledError(
|
||||
delta: Matrix1,
|
||||
maxIndices: Matrix1,
|
||||
batch: Int64,
|
||||
inH: Int64,
|
||||
inW: Int64,
|
||||
outH: Int64,
|
||||
outW: Int64
|
||||
): Matrix1 {
|
||||
let inSize = batch * inH * inW
|
||||
var errsUp = Matrix1(inSize, nChannels)
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (oh in 0..outH) {
|
||||
for (ow in 0..outW) {
|
||||
let deltaRow = b * outH * outW + oh * outW + ow
|
||||
|
||||
for (ch in 0..nChannels) {
|
||||
let deltaVal = delta.get(deltaRow, ch).getOrThrow()
|
||||
let w = weights.get(0, ch).getOrThrow()
|
||||
|
||||
if (poolType == "max") {
|
||||
let maxIdx = Int64(maxIndices.get(deltaRow, ch).getOrThrow().getValue())
|
||||
|
||||
var kernelIdx = 0
|
||||
var found = false
|
||||
for (kh in 0..poolHeight) {
|
||||
for (kw in 0..poolWidth) {
|
||||
if (kernelIdx == maxIdx && !found) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
errsUp.set(inRow, ch, deltaVal * w)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
kernelIdx += 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var count = 0
|
||||
for (kh in 0..poolHeight) {
|
||||
for (kw in 0..poolWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
let avgDelta = deltaVal * w / FP32(Float32(count))
|
||||
for (kh in 0..poolHeight) {
|
||||
for (kw in 0..poolWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
let oldVal = errsUp.get(inRow, ch).getOrThrow()
|
||||
errsUp.set(inRow, ch, oldVal + avgDelta)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errsUp
|
||||
}
|
||||
|
||||
// ==================== 参数更新 ====================
|
||||
public func update(discount: Float32): Unit {
|
||||
var wGrad = Matrix1(1, 1)
|
||||
match (weightsGrad) {
|
||||
case Some(val) => wGrad = val
|
||||
case _ => throw Exception("No weight gradients")
|
||||
}
|
||||
|
||||
var bGrad = Matrix1(1, 1)
|
||||
match (biasGrad) {
|
||||
case Some(val) => bGrad = val
|
||||
case _ => throw Exception("No bias gradients")
|
||||
}
|
||||
|
||||
for (ch in 0..nChannels) {
|
||||
let oldW = weights.get(0, ch).getOrThrow()
|
||||
let gW = wGrad.get(0, ch).getOrThrow()
|
||||
weights.set(0, ch, oldW + gW * learningRateWeight * FP32(discount))
|
||||
}
|
||||
|
||||
for (ch in 0..nChannels) {
|
||||
let oldB = biases.get(0, ch).getOrThrow()
|
||||
let gB = bGrad.get(0, ch).getOrThrow()
|
||||
biases.set(0, ch, oldB + gB * learningRateBias * FP32(discount))
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 形状访问器 ====================
|
||||
public func getInShape(): (Int64, Int64) {
|
||||
(nChannels, poolHeight * poolWidth)
|
||||
}
|
||||
|
||||
public func getOutShape(): Int64 {
|
||||
nChannels
|
||||
}
|
||||
|
||||
public func getPoolType(): String {
|
||||
poolType
|
||||
}
|
||||
|
||||
public func getLR(): FP32 {
|
||||
learningRateWeight
|
||||
}
|
||||
|
||||
public func getFType(): String {
|
||||
f.get_type()
|
||||
}
|
||||
|
||||
public func calculateOutputSize(inputHeight: Int64, inputWidth: Int64): (Int64, Int64) {
|
||||
let outH = (inputHeight + 2 * padding - poolHeight) / stride + 1
|
||||
let outW = (inputWidth + 2 * padding - poolWidth) / stride + 1
|
||||
(outH, outW)
|
||||
}
|
||||
}
|
||||
+1071
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,310 @@
|
||||
// test_cnn.cj - 使用 Adam 优化器的版本(所有层)
|
||||
package bot4cj
|
||||
import bot4cj.math.*
|
||||
import std.math.*
|
||||
import std.random.*
|
||||
import std.collection.*
|
||||
|
||||
func generateSolidColorData(numSamples: Int64): (Array<Matrix1>, Array<Int64>) {
|
||||
var inputs = Array(numSamples, { _ => Matrix1(1, 1) })
|
||||
var labels = Array(numSamples, { _ => 0 })
|
||||
let rng = Random()
|
||||
|
||||
for (i in 0..numSamples) {
|
||||
let colorClass = Int64(rng.nextFloat64() * 3.0)
|
||||
labels[i] = colorClass
|
||||
|
||||
let h = 8
|
||||
let w = 8
|
||||
var matrix = Matrix1(h * w, 1)
|
||||
|
||||
let colorValue = match (colorClass) {
|
||||
case 0 => FP32(1.0)
|
||||
case 1 => FP32(0.5)
|
||||
case _ => FP32(0.0)
|
||||
}
|
||||
|
||||
for (r in 0..h * w) {
|
||||
let noise = FP32(Float32(rng.nextFloat64() - 0.5) * 0.1)
|
||||
var val = colorValue + noise
|
||||
if (val.getValue() > 1.0) {
|
||||
val = FP32(1.0)
|
||||
} else if (val.getValue() < 0.0) {
|
||||
val = FP32(0.0)
|
||||
}
|
||||
matrix.set(r, 0, val)
|
||||
}
|
||||
|
||||
inputs[i] = matrix
|
||||
}
|
||||
|
||||
(inputs, labels)
|
||||
}
|
||||
|
||||
func printSeparator(): Unit {
|
||||
println("==================================================")
|
||||
}
|
||||
|
||||
func testCNN(): Unit {
|
||||
printSeparator()
|
||||
println("CNN 纯色矩阵识别测试 (所有层使用 Adam 优化器)")
|
||||
printSeparator()
|
||||
println()
|
||||
|
||||
let nTrainSamples = 200
|
||||
let nTestSamples = 60
|
||||
let nEpochs = 50
|
||||
let batchSize = 10
|
||||
|
||||
println("训练样本数: ${nTrainSamples}")
|
||||
println("测试样本数: ${nTestSamples}")
|
||||
println("训练轮数: ${nEpochs}")
|
||||
println("Batch 大小: ${batchSize}")
|
||||
println()
|
||||
|
||||
println("创建网络结构...")
|
||||
println("Input(8x8x1) -> Conv(3x3,1->4) -> Pool(2x2) -> Conv(3x3,4->8) -> Flatten -> FC(8,3)")
|
||||
println()
|
||||
|
||||
let lr = FP32(0.001)
|
||||
|
||||
var conv1 = ConvLayer(1, 4, (3, 3), 1, 0, lr, Af.Leaky_ReLU(FP32(0.01)), Option.None)
|
||||
var pool1 = PoolingLayer(4, (2, 2), 2, 0, "max", Af.ReLU, lr, lr)
|
||||
var conv2 = ConvLayer(4, 8, (3, 3), 1, 0, lr, Af.Leaky_ReLU(FP32(0.01)), Option.None)
|
||||
var flatten = FlattenLayer(8, 1, 1)
|
||||
var fc = Nn1(8, 3, Float32(0.4), Af.Softmax)
|
||||
|
||||
// 创建 Adam 优化器(为每个可训练层创建独立的优化器)
|
||||
var adamConv1 = Adam(FP32(0.001)) // 卷积层1
|
||||
var adamConv2 = Adam(FP32(0.001)) // 卷积层2
|
||||
var adamFC = Adam(FP32(0.0005)) // 全连接层(使用更小的学习率)
|
||||
|
||||
println("网络创建完成 (Conv1, Conv2, FC 使用 Adam 优化器)")
|
||||
println()
|
||||
|
||||
println("生成数据...")
|
||||
let (trainInputs, trainLabels) = generateSolidColorData(nTrainSamples)
|
||||
let (testInputs, testLabels) = generateSolidColorData(nTestSamples)
|
||||
println("训练集: ${nTrainSamples} 样本")
|
||||
println("测试集: ${nTestSamples} 样本")
|
||||
println()
|
||||
|
||||
println("开始训练...")
|
||||
println()
|
||||
|
||||
for (epoch in 0..nEpochs) {
|
||||
var epochLoss = Float32(0.0)
|
||||
var epochCorrect = 0
|
||||
|
||||
for (i in 0..nTrainSamples) {
|
||||
let x = trainInputs[i]
|
||||
let label = trainLabels[i]
|
||||
|
||||
// ===== 前向传播 =====
|
||||
let c1Out = conv1.forward(x, (1, 8, 8, 1))
|
||||
let p1Out = pool1.forward(c1Out, (1, 6, 6, 4))
|
||||
let c2Out = conv2.forward(p1Out, (1, 3, 3, 4))
|
||||
let flatOut = flatten.forward(c2Out, (1, 1, 1, 8))
|
||||
|
||||
// 调试:确认 flatten.forward 执行成功
|
||||
if (i == 0 && epoch == 0) {
|
||||
println("DEBUG: flatten.forward done, flatOut rows: ${flatOut.get_row()}, cols: ${flatOut.get_col()}")
|
||||
}
|
||||
|
||||
var fcInput = Matrix1(1, 8)
|
||||
for (j in 0..8) {
|
||||
fcInput.set(0, j, flatOut.get(j, 0).getOrThrow())
|
||||
}
|
||||
let fcOut = fc.forward(fcInput)
|
||||
|
||||
// Softmax
|
||||
var softmax = Matrix1(1, 3)
|
||||
var maxVal = Float32(-1.0e30)
|
||||
for (c in 0..3) {
|
||||
let raw = fcOut.get(0, c).getOrThrow().getValue()
|
||||
if (raw > maxVal) {
|
||||
maxVal = raw
|
||||
}
|
||||
}
|
||||
var expSum = Float32(0.0)
|
||||
for (c in 0..3) {
|
||||
let raw = fcOut.get(0, c).getOrThrow().getValue()
|
||||
let ev = Float32(exp(raw - maxVal))
|
||||
softmax.set(0, c, FP32(ev))
|
||||
expSum = expSum + ev
|
||||
}
|
||||
for (c in 0..3) {
|
||||
let v = softmax.get(0, c).getOrThrow().getValue()
|
||||
softmax.set(0, c, FP32(v / expSum))
|
||||
}
|
||||
|
||||
let probVal = softmax.get(0, label).getOrThrow().getValue()
|
||||
let loss = -log(max(probVal, Float32(1e-10)))
|
||||
epochLoss = epochLoss + loss
|
||||
|
||||
var maxProb = Float32(-1.0)
|
||||
var predClass = 0
|
||||
for (c in 0..3) {
|
||||
let p = softmax.get(0, c).getOrThrow().getValue()
|
||||
if (p > maxProb) {
|
||||
maxProb = p
|
||||
predClass = Int64(c)
|
||||
}
|
||||
}
|
||||
if (predClass == label) {
|
||||
epochCorrect += 1
|
||||
}
|
||||
|
||||
// ===== 反向传播 =====
|
||||
var delta = Matrix1(1, 3)
|
||||
for (c in 0..3) {
|
||||
let prob = softmax.get(0, c).getOrThrow()
|
||||
if (Int64(c) == label) {
|
||||
delta.set(0, c, prob - FP32(1.0))
|
||||
} else {
|
||||
delta.set(0, c, prob)
|
||||
}
|
||||
}
|
||||
|
||||
let fcDelta = fc.backward(delta)
|
||||
|
||||
// 调试:确认 fcDelta 形状
|
||||
if (i == 0 && epoch == 0) {
|
||||
println("DEBUG: fc.backward done, fcDelta rows: ${fcDelta.get_row()}, cols: ${fcDelta.get_col()}")
|
||||
}
|
||||
|
||||
var flatDelta = Matrix1(8, 1)
|
||||
for (j in 0..8) {
|
||||
flatDelta.set(j, 0, fcDelta.get(0, j).getOrThrow())
|
||||
}
|
||||
|
||||
// 调试:确认 flatDelta 形状
|
||||
if (i == 0 && epoch == 0) {
|
||||
println("DEBUG: flatDelta rows: ${flatDelta.get_row()}, cols: ${flatDelta.get_col()}")
|
||||
}
|
||||
|
||||
let c2Delta = flatten.backward(flatDelta)
|
||||
let p1Delta = conv2.backward(c2Delta)
|
||||
let c1Delta = pool1.backward(p1Delta)
|
||||
let _ = conv1.backward(c1Delta)
|
||||
|
||||
// ===== 参数更新 =====
|
||||
if ((i + 1) % batchSize == 0) {
|
||||
// 所有可训练层使用 Adam 更新
|
||||
adamConv1.updateConvLayer(conv1) // 卷积层1 - Adam
|
||||
adamConv2.updateConvLayer(conv2) // 卷积层2 - Adam
|
||||
adamFC.updateFCLayer(fc) // 全连接层 - Adam
|
||||
|
||||
// 池化层使用 SGD 更新(如果需要的话)
|
||||
pool1.update(Float32(1.0))
|
||||
}
|
||||
}
|
||||
|
||||
// Epoch 结束时的更新
|
||||
adamConv1.updateConvLayer(conv1)
|
||||
adamConv2.updateConvLayer(conv2)
|
||||
adamFC.updateFCLayer(fc)
|
||||
pool1.update(Float32(1.0))
|
||||
|
||||
let avgLoss = epochLoss / Float32(nTrainSamples)
|
||||
let accuracy = Float32(epochCorrect) / Float32(nTrainSamples) * Float32(100.0)
|
||||
|
||||
println("Epoch ${epoch + 1}/${nEpochs} - Loss: ${avgLoss}, Accuracy: ${accuracy}%")
|
||||
|
||||
// 打印 Adam 的步数信息
|
||||
if (epoch == 0) {
|
||||
println(" Adam Step: ${adamConv1.getStep()}")
|
||||
}
|
||||
}
|
||||
|
||||
println("")
|
||||
println("开始测试...")
|
||||
println("")
|
||||
|
||||
var testCorrect = 0
|
||||
var confusion = Array(3, { _ => Array(3, { _ => 0 }) })
|
||||
|
||||
for (i in 0..nTestSamples) {
|
||||
let x = testInputs[i]
|
||||
let label = testLabels[i]
|
||||
|
||||
let c1Out = conv1.forward(x, (1, 8, 8, 1))
|
||||
let p1Out = pool1.forward(c1Out, (1, 6, 6, 4))
|
||||
let c2Out = conv2.forward(p1Out, (1, 3, 3, 4))
|
||||
let flatOut = flatten.forward(c2Out, (1, 1, 1, 8))
|
||||
|
||||
var fcInput = Matrix1(1, 8)
|
||||
for (j in 0..8) {
|
||||
fcInput.set(0, j, flatOut.get(j, 0).getOrThrow())
|
||||
}
|
||||
let fcOut = fc.forward(fcInput)
|
||||
|
||||
var softmax = Matrix1(1, 3)
|
||||
var maxVal = Float32(-1.0e30)
|
||||
for (c in 0..3) {
|
||||
let raw = fcOut.get(0, c).getOrThrow().getValue()
|
||||
if (raw > maxVal) {
|
||||
maxVal = raw
|
||||
}
|
||||
}
|
||||
var expSum = Float32(0.0)
|
||||
for (c in 0..3) {
|
||||
let raw = fcOut.get(0, c).getOrThrow().getValue()
|
||||
let ev = Float32(exp(raw - maxVal))
|
||||
softmax.set(0, c, FP32(ev))
|
||||
expSum = expSum + ev
|
||||
}
|
||||
for (c in 0..3) {
|
||||
let v = softmax.get(0, c).getOrThrow().getValue()
|
||||
softmax.set(0, c, FP32(v / expSum))
|
||||
}
|
||||
|
||||
var maxProb = Float32(-1.0)
|
||||
var predClass = 0
|
||||
for (c in 0..3) {
|
||||
let p = softmax.get(0, c).getOrThrow().getValue()
|
||||
if (p > maxProb) {
|
||||
maxProb = p
|
||||
predClass = Int64(c)
|
||||
}
|
||||
}
|
||||
|
||||
confusion[label][predClass] += 1
|
||||
if (predClass == label) {
|
||||
testCorrect += 1
|
||||
}
|
||||
}
|
||||
|
||||
let testAcc = Float32(testCorrect) / Float32(nTestSamples) * Float32(100.0)
|
||||
println("测试准确率: ${testAcc}% (${testCorrect}/${nTestSamples})")
|
||||
print("")
|
||||
|
||||
let classNames = ["红色(高)", "绿色(中)", "蓝色(低)"]
|
||||
println("各类别准确率:")
|
||||
for (c in 0..3) {
|
||||
var total = 0
|
||||
for (p in 0..3) {
|
||||
total += confusion[c][p]
|
||||
}
|
||||
if (total > 0) {
|
||||
let acc = Float32(confusion[c][c]) / Float32(total) * Float32(100.0)
|
||||
print(" ${classNames[c]}: ${confusion[c][c]}/${total} (${acc}%)")
|
||||
}
|
||||
}
|
||||
println("")
|
||||
|
||||
println("混淆矩阵:")
|
||||
println(" 预测红 预测绿 预测蓝")
|
||||
for (t in 0..3) {
|
||||
var line = "真实${classNames[t]}:"
|
||||
for (p in 0..3) {
|
||||
line = line + " ${confusion[t][p]} "
|
||||
}
|
||||
print(line)
|
||||
}
|
||||
println("")
|
||||
|
||||
printSeparator()
|
||||
println("测试完成!")
|
||||
printSeparator()
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
// test_geometry_cnn.cj - 几何图形分类测试(Adam 优化器版本)
|
||||
package bot4cj
|
||||
import bot4cj.math.*
|
||||
import std.math.*
|
||||
import std.random.*
|
||||
import std.collection.*
|
||||
|
||||
/**
|
||||
* 计算圆形像素值
|
||||
*/
|
||||
func calcCirclePixel(x: Int64, y: Int64, center: Float64, radius: Float64, offsetX: Float64, offsetY: Float64): FP32 {
|
||||
let dx = Float64(x) - center + offsetX
|
||||
let dy = Float64(y) - center + offsetY
|
||||
let dist = sqrt(dx * dx + dy * dy)
|
||||
|
||||
if (dist <= radius) {
|
||||
let intensity = 1.0 - (dist / radius) * 0.3
|
||||
FP32(Float32(intensity))
|
||||
} else if (dist <= radius + 1.5) {
|
||||
let edgeDist = dist - radius
|
||||
let intensity = max(0.0, 1.0 - edgeDist / 1.5) * 0.7
|
||||
FP32(Float32(intensity))
|
||||
} else {
|
||||
FP32(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算正方形像素值
|
||||
*/
|
||||
func calcSquarePixel(x: Int64, y: Int64, center: Float64, radius: Float64, offsetX: Float64, offsetY: Float64): FP32 {
|
||||
let halfSize = radius * 0.8
|
||||
let left = center - halfSize + offsetX
|
||||
let right = center + halfSize + offsetX
|
||||
let top = center - halfSize + offsetY
|
||||
let bottom = center + halfSize + offsetY
|
||||
|
||||
let fx = Float64(x)
|
||||
let fy = Float64(y)
|
||||
|
||||
if (fx >= left && fx <= right && fy >= top && fy <= bottom) {
|
||||
let distToEdge = min(
|
||||
min(fx - left, right - fx),
|
||||
min(fy - top, bottom - fy)
|
||||
)
|
||||
let intensity = min(1.0, 0.7 + distToEdge / halfSize * 0.3)
|
||||
FP32(Float32(intensity))
|
||||
} else {
|
||||
let distX = max(0.0, max(left - fx, fx - right))
|
||||
let distY = max(0.0, max(top - fy, fy - bottom))
|
||||
let edgeDist = sqrt(distX * distX + distY * distY)
|
||||
if (edgeDist <= 1.5) {
|
||||
let intensity = max(0.0, 1.0 - edgeDist / 1.5) * 0.6
|
||||
FP32(Float32(intensity))
|
||||
} else {
|
||||
FP32(0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算三角形像素值
|
||||
*/
|
||||
func calcTrianglePixel(x: Int64, y: Int64, center: Float64, radius: Float64, offsetX: Float64, offsetY: Float64): FP32 {
|
||||
let height = radius * 2.0
|
||||
let baseWidth = height * 1.2
|
||||
let topY = center - height * 0.5 + offsetY
|
||||
let bottomY = center + height * 0.5 + offsetY
|
||||
|
||||
let fx = Float64(x)
|
||||
let fy = Float64(y)
|
||||
|
||||
if (fy >= topY && fy <= bottomY) {
|
||||
let t = (fy - topY) / (bottomY - topY)
|
||||
let halfWidthAtY = baseWidth * (1.0 - t) * 0.5
|
||||
let centerX = center + offsetX
|
||||
|
||||
if (fx >= centerX - halfWidthAtY && fx <= centerX + halfWidthAtY) {
|
||||
let distToEdge = min(
|
||||
fx - (centerX - halfWidthAtY),
|
||||
(centerX + halfWidthAtY) - fx
|
||||
)
|
||||
let intensity = min(1.0, 0.7 + distToEdge / halfWidthAtY * 0.3)
|
||||
FP32(Float32(intensity))
|
||||
} else {
|
||||
let distToLeft = (centerX - halfWidthAtY) - fx
|
||||
let distToRight = fx - (centerX + halfWidthAtY)
|
||||
let edgeDist = max(distToLeft, distToRight)
|
||||
if (edgeDist <= 1.5) {
|
||||
let intensity = max(0.0, 1.0 - edgeDist / 1.5) * 0.6
|
||||
FP32(Float32(intensity))
|
||||
} else {
|
||||
FP32(0.0)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FP32(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成几何图形数据
|
||||
* 图形类型:0=圆形, 1=正方形, 2=三角形
|
||||
*/
|
||||
func generateGeometryData(numSamples: Int64): (Array<Matrix1>, Array<Int64>) {
|
||||
var inputs = Array(numSamples, { _ => Matrix1(1, 1) })
|
||||
var labels = Array(numSamples, { _ => 0 })
|
||||
let rng = Random()
|
||||
|
||||
let imgSize = 16
|
||||
|
||||
for (i in 0..numSamples) {
|
||||
let shapeType = Int64(rng.nextFloat64() * 3.0)
|
||||
labels[i] = shapeType
|
||||
|
||||
var matrix = Matrix1(imgSize * imgSize, 1)
|
||||
let center = Float64(imgSize) / 2.0
|
||||
let radius = Float64(imgSize) / 3.0 + rng.nextFloat64() * 2.0 - 1.0
|
||||
|
||||
let offsetX = (rng.nextFloat64() - 0.5) * 2.0
|
||||
let offsetY = (rng.nextFloat64() - 0.5) * 2.0
|
||||
|
||||
for (y in 0..imgSize) {
|
||||
for (x in 0..imgSize) {
|
||||
let idx = y * imgSize + x
|
||||
|
||||
var pixelValue = match (shapeType) {
|
||||
case 0 => calcCirclePixel(x, y, center, radius, offsetX, offsetY)
|
||||
case 1 => calcSquarePixel(x, y, center, radius, offsetX, offsetY)
|
||||
case _ => calcTrianglePixel(x, y, center, radius, offsetX, offsetY)
|
||||
}
|
||||
|
||||
let noise = FP32(Float32(rng.nextFloat64() - 0.5) * 0.05)
|
||||
pixelValue = pixelValue + noise
|
||||
|
||||
if (pixelValue.getValue() > 1.0) {
|
||||
pixelValue = FP32(1.0)
|
||||
} else if (pixelValue.getValue() < 0.0) {
|
||||
pixelValue = FP32(0.0)
|
||||
}
|
||||
|
||||
matrix.set(idx, 0, pixelValue)
|
||||
}
|
||||
}
|
||||
|
||||
inputs[i] = matrix
|
||||
}
|
||||
|
||||
(inputs, labels)
|
||||
}
|
||||
|
||||
/**
|
||||
* 可视化一个样本
|
||||
*/
|
||||
func showSample(matrix: Matrix1, imgSize: Int64, label: Int64): Unit {
|
||||
let shapeNames = ["圆形", "正方形", "三角形"]
|
||||
println("标签: ${shapeNames[label]}")
|
||||
|
||||
for (y in 0..imgSize) {
|
||||
var line = ""
|
||||
for (x in 0..imgSize) {
|
||||
let idx = y * imgSize + x
|
||||
let val = matrix.get(idx, 0).getOrThrow().getValue()
|
||||
if (val > 0.8) {
|
||||
line = line + "██"
|
||||
} else if (val > 0.6) {
|
||||
line = line + "▓▓"
|
||||
} else if (val > 0.4) {
|
||||
line = line + "▒▒"
|
||||
} else if (val > 0.2) {
|
||||
line = line + "░░"
|
||||
} else {
|
||||
line = line + " "
|
||||
}
|
||||
}
|
||||
println(line)
|
||||
}
|
||||
println("")
|
||||
}
|
||||
|
||||
/**
|
||||
* Softmax 辅助函数
|
||||
*/
|
||||
func computeSoftmax(input: Matrix1): Matrix1 {
|
||||
let cols = input.get_col()
|
||||
var output = Matrix1(1, cols)
|
||||
|
||||
var maxVal = Float32(-1.0e30)
|
||||
for (c in 0..cols) {
|
||||
let raw = input.get(0, c).getOrThrow().getValue()
|
||||
if (raw > maxVal) {
|
||||
maxVal = raw
|
||||
}
|
||||
}
|
||||
|
||||
var expSum = Float32(0.0)
|
||||
for (c in 0..cols) {
|
||||
let raw = input.get(0, c).getOrThrow().getValue()
|
||||
let ev = Float32(exp(raw - maxVal))
|
||||
output.set(0, c, FP32(ev))
|
||||
expSum = expSum + ev
|
||||
}
|
||||
|
||||
for (c in 0..cols) {
|
||||
let v = output.get(0, c).getOrThrow().getValue()
|
||||
output.set(0, c, FP32(v / expSum))
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预测类别
|
||||
*/
|
||||
func predictClass(probs: Matrix1): Int64 {
|
||||
let cols = probs.get_col()
|
||||
var maxProb = Float32(-1.0)
|
||||
var predClass = 0
|
||||
|
||||
for (c in 0..cols) {
|
||||
let p = probs.get(0, c).getOrThrow().getValue()
|
||||
if (p > maxProb) {
|
||||
maxProb = p
|
||||
predClass = Int64(c)
|
||||
}
|
||||
}
|
||||
|
||||
predClass
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印分隔线
|
||||
*/
|
||||
func printLine(): Unit {
|
||||
println("============================================================")
|
||||
}
|
||||
|
||||
/**
|
||||
* 主测试函数
|
||||
*/
|
||||
func testGeometryCNN(): Unit {
|
||||
printLine()
|
||||
println("CNN 几何图形分类测试 (Adam 优化器版本)")
|
||||
printLine()
|
||||
println("")
|
||||
|
||||
let imgSize = 16
|
||||
let nTrainSamples = 200
|
||||
let nTestSamples = 60
|
||||
let nEpochs = 50
|
||||
let batchSize = 10
|
||||
let nClasses = 3
|
||||
|
||||
println("配置信息:")
|
||||
println(" 图像大小: ${imgSize}x${imgSize}")
|
||||
println(" 训练样本数: ${nTrainSamples}")
|
||||
println(" 测试样本数: ${nTestSamples}")
|
||||
println(" 训练轮数: ${nEpochs}")
|
||||
println(" Batch 大小: ${batchSize}")
|
||||
println(" 类别数: ${nClasses} (圆形, 正方形, 三角形)")
|
||||
println(" 优化器: Adam")
|
||||
println("")
|
||||
|
||||
println("网络结构:")
|
||||
println(" Input(16x16x1)")
|
||||
println(" -> Conv1(5x5, 1->8) -> Pool1(2x2)")
|
||||
println(" -> Conv2(3x3, 8->16) -> Pool2(2x2)")
|
||||
println(" -> Flatten -> FC(64->32) -> FC(32->3)")
|
||||
println(" -> Softmax")
|
||||
println("")
|
||||
|
||||
let lr = FP32(0.01)
|
||||
|
||||
// 构建网络
|
||||
var conv1 = ConvLayer(1, 8, (5, 5), 1, 0, lr, Af.Leaky_ReLU(FP32(0.01)), Option.None)
|
||||
var pool1 = PoolingLayer(8, (2, 2), 2, 0, "max", Af.ReLU, lr, lr)
|
||||
var conv2 = ConvLayer(8, 16, (3, 3), 1, 0, lr, Af.Leaky_ReLU(FP32(0.01)), Option.None)
|
||||
var pool2 = PoolingLayer(16, (2, 2), 2, 0, "max", Af.ReLU, lr, lr)
|
||||
var flatten = FlattenLayer(16, 2, 2)
|
||||
var fc1 = Nn1(64, 32, Float32(0.3), Af.Leaky_ReLU(FP32(0.01)))
|
||||
var fc2 = Nn1(32, 3, Float32(0.3), Af.Softmax)
|
||||
|
||||
// 创建 Adam 优化器
|
||||
var adamConv1 = Adam(FP32(0.001)) // 卷积层1
|
||||
var adamConv2 = Adam(FP32(0.001)) // 卷积层2
|
||||
var adamFC1 = Adam(FP32(0.0005)) // 全连接层1
|
||||
var adamFC2 = Adam(FP32(0.0005)) // 全连接层2
|
||||
|
||||
println("网络创建完成 (Adam优化器: Conv1, Conv2, FC1, FC2)")
|
||||
println("")
|
||||
|
||||
// 生成数据
|
||||
println("生成训练数据...")
|
||||
let (trainInputs, trainLabels) = generateGeometryData(nTrainSamples)
|
||||
println("生成测试数据...")
|
||||
let (testInputs, testLabels) = generateGeometryData(nTestSamples)
|
||||
println("数据生成完成")
|
||||
println("")
|
||||
|
||||
// 显示几个样本
|
||||
println("样本预览:")
|
||||
for (i in 0..3) {
|
||||
if (i < nTrainSamples) {
|
||||
println("样本 ${i + 1}:")
|
||||
showSample(trainInputs[i], imgSize, trainLabels[i])
|
||||
}
|
||||
}
|
||||
|
||||
println("开始训练...")
|
||||
println("")
|
||||
|
||||
var bestAccuracy = Float32(0.0)
|
||||
|
||||
// 计算卷积输出尺寸
|
||||
let conv1OutH = imgSize - 4 // 16 - 5 + 1 = 12
|
||||
let conv1OutW = imgSize - 4
|
||||
let pool1OutH = conv1OutH / 2 // 6
|
||||
let pool1OutW = conv1OutW / 2
|
||||
let conv2OutH = pool1OutH - 2 // 6 - 3 + 1 = 4
|
||||
let conv2OutW = pool1OutW - 2
|
||||
let pool2OutH = conv2OutH / 2 // 2
|
||||
let pool2OutW = conv2OutW / 2
|
||||
let flatSize = 16 * pool2OutH * pool2OutW // 64
|
||||
|
||||
for (epoch in 0..nEpochs) {
|
||||
var epochLoss = Float32(0.0)
|
||||
var epochCorrect = 0
|
||||
|
||||
// 训练
|
||||
for (i in 0..nTrainSamples) {
|
||||
let x = trainInputs[i]
|
||||
let label = trainLabels[i]
|
||||
|
||||
// 前向传播
|
||||
let c1Out = conv1.forward(x, (1, imgSize, imgSize, 1))
|
||||
let p1Out = pool1.forward(c1Out, (1, conv1OutH, conv1OutW, 8))
|
||||
let c2Out = conv2.forward(p1Out, (1, pool1OutH, pool1OutW, 8))
|
||||
let p2Out = pool2.forward(c2Out, (1, conv2OutH, conv2OutW, 16))
|
||||
|
||||
let flatOut = flatten.forward(p2Out, (1, pool2OutH, pool2OutW, 16))
|
||||
|
||||
// 转换为全连接层输入
|
||||
var fc1Input = Matrix1(1, flatSize)
|
||||
for (j in 0..flatSize) {
|
||||
fc1Input.set(0, j, flatOut.get(j, 0).getOrThrow())
|
||||
}
|
||||
|
||||
let fc1Out = fc1.forward(fc1Input)
|
||||
let fc2Out = fc2.forward(fc1Out)
|
||||
|
||||
// Softmax
|
||||
let probs = computeSoftmax(fc2Out)
|
||||
|
||||
// 计算损失和准确率
|
||||
let probVal = probs.get(0, label).getOrThrow().getValue()
|
||||
let loss = -log(max(probVal, Float32(1e-10)))
|
||||
epochLoss = epochLoss + loss
|
||||
|
||||
let predClass = predictClass(probs)
|
||||
if (predClass == label) {
|
||||
epochCorrect += 1
|
||||
}
|
||||
|
||||
// 反向传播
|
||||
var delta = Matrix1(1, nClasses)
|
||||
for (c in 0..nClasses) {
|
||||
let prob = probs.get(0, c).getOrThrow()
|
||||
if (Int64(c) == label) {
|
||||
delta.set(0, c, prob - FP32(1.0))
|
||||
} else {
|
||||
delta.set(0, c, prob)
|
||||
}
|
||||
}
|
||||
|
||||
let fc2Delta = fc2.backward(delta)
|
||||
let fc1Delta = fc1.backward(fc2Delta)
|
||||
|
||||
// 转换回特征图格式
|
||||
var flatDelta = Matrix1(flatSize, 1)
|
||||
for (j in 0..flatSize) {
|
||||
flatDelta.set(j, 0, fc1Delta.get(0, j).getOrThrow())
|
||||
}
|
||||
|
||||
let p2Delta = flatten.backward(flatDelta)
|
||||
let c2Delta = pool2.backward(p2Delta)
|
||||
let p1Delta = conv2.backward(c2Delta)
|
||||
let c1Delta = pool1.backward(p1Delta)
|
||||
let _ = conv1.backward(c1Delta)
|
||||
|
||||
// 批次更新 - 使用 Adam
|
||||
if ((i + 1) % batchSize == 0) {
|
||||
adamConv1.updateConvLayer(conv1) // Conv1 - Adam
|
||||
adamConv2.updateConvLayer(conv2) // Conv2 - Adam
|
||||
adamFC1.updateFCLayer(fc1) // FC1 - Adam
|
||||
adamFC2.updateFCLayer(fc2) // FC2 - Adam
|
||||
|
||||
// 池化层仍使用 SGD
|
||||
pool1.update(Float32(1.0))
|
||||
pool2.update(Float32(1.0))
|
||||
}
|
||||
}
|
||||
|
||||
// 最终更新 - 使用 Adam
|
||||
adamConv1.updateConvLayer(conv1)
|
||||
adamConv2.updateConvLayer(conv2)
|
||||
adamFC1.updateFCLayer(fc1)
|
||||
adamFC2.updateFCLayer(fc2)
|
||||
pool1.update(Float32(1.0))
|
||||
pool2.update(Float32(1.0))
|
||||
|
||||
let avgLoss = epochLoss / Float32(nTrainSamples)
|
||||
let accuracy = Float32(epochCorrect) / Float32(nTrainSamples) * Float32(100.0)
|
||||
|
||||
if (accuracy > bestAccuracy) {
|
||||
bestAccuracy = accuracy
|
||||
}
|
||||
|
||||
// 输出训练信息
|
||||
if (epoch % 1 == 0 || epoch == nEpochs - 1) {
|
||||
println("Epoch ${epoch + 1}/${nEpochs} - Loss: ${avgLoss}, Accuracy: ${accuracy}%, Adam Step: ${adamConv1.getStep()}")
|
||||
}
|
||||
}
|
||||
|
||||
println("")
|
||||
println("训练完成! 最佳训练准确率: ${bestAccuracy}%")
|
||||
println("总更新步数: ${adamConv1.getStep()}")
|
||||
println("")
|
||||
|
||||
// 测试
|
||||
println("开始测试...")
|
||||
println("")
|
||||
|
||||
var testCorrect = 0
|
||||
var confusion = Array(nClasses, { _ => Array(nClasses, { _ => 0 }) })
|
||||
var testLoss = Float32(0.0)
|
||||
|
||||
for (i in 0..nTestSamples) {
|
||||
let x = testInputs[i]
|
||||
let label = testLabels[i]
|
||||
|
||||
// 前向传播
|
||||
let c1Out = conv1.forward(x, (1, imgSize, imgSize, 1))
|
||||
let p1Out = pool1.forward(c1Out, (1, conv1OutH, conv1OutW, 8))
|
||||
let c2Out = conv2.forward(p1Out, (1, pool1OutH, pool1OutW, 8))
|
||||
let p2Out = pool2.forward(c2Out, (1, conv2OutH, conv2OutW, 16))
|
||||
|
||||
let flatOut = flatten.forward(p2Out, (1, pool2OutH, pool2OutW, 16))
|
||||
|
||||
var fc1Input = Matrix1(1, flatSize)
|
||||
for (j in 0..flatSize) {
|
||||
fc1Input.set(0, j, flatOut.get(j, 0).getOrThrow())
|
||||
}
|
||||
|
||||
let fc1Out = fc1.forward(fc1Input)
|
||||
let fc2Out = fc2.forward(fc1Out)
|
||||
|
||||
let probs = computeSoftmax(fc2Out)
|
||||
|
||||
// 计算损失
|
||||
let probVal = probs.get(0, label).getOrThrow().getValue()
|
||||
testLoss = testLoss + Float32(-log(max(probVal, Float32(1e-10))))
|
||||
|
||||
// 预测
|
||||
let predClass = predictClass(probs)
|
||||
|
||||
confusion[label][predClass] += 1
|
||||
if (predClass == label) {
|
||||
testCorrect += 1
|
||||
}
|
||||
|
||||
// 显示前几个错误分类的样本
|
||||
if (predClass != label && testCorrect < 10) {
|
||||
println("错误分类样本 ${i + 1}:")
|
||||
showSample(x, imgSize, label)
|
||||
let shapeNames = ["圆形", "正方形", "三角形"]
|
||||
println(" 真实标签: ${shapeNames[label]}, 预测标签: ${shapeNames[predClass]}")
|
||||
println("")
|
||||
}
|
||||
}
|
||||
|
||||
let testAcc = Float32(testCorrect) / Float32(nTestSamples) * Float32(100.0)
|
||||
let avgTestLoss = testLoss / Float32(nTestSamples)
|
||||
|
||||
println("测试结果:")
|
||||
println(" 准确率: ${testAcc}% (${testCorrect}/${nTestSamples})")
|
||||
println(" 平均损失: ${avgTestLoss}")
|
||||
println("")
|
||||
|
||||
let classNames = ["圆形 ", "正方形", "三角形"]
|
||||
println("各类别准确率:")
|
||||
for (c in 0..nClasses) {
|
||||
var total = 0
|
||||
for (p in 0..nClasses) {
|
||||
total += confusion[c][p]
|
||||
}
|
||||
if (total > 0) {
|
||||
let acc = Float32(confusion[c][c]) / Float32(total) * Float32(100.0)
|
||||
println(" ${classNames[c]}: ${confusion[c][c]}/${total} (${acc}%)")
|
||||
}
|
||||
}
|
||||
println("")
|
||||
|
||||
println("混淆矩阵:")
|
||||
println(" 预测圆形 预测正方形 预测三角形")
|
||||
for (t in 0..nClasses) {
|
||||
var line = "真实${classNames[t]}:"
|
||||
for (p in 0..nClasses) {
|
||||
line = line + " ${confusion[t][p]} "
|
||||
}
|
||||
println(line)
|
||||
}
|
||||
println("")
|
||||
|
||||
printLine()
|
||||
println("几何图形分类测试完成!")
|
||||
printLine()
|
||||
}
|
||||
Reference in new issue
Block a user