diff --git a/bot4cj/cjpm.toml b/bot4cj/cjpm.toml index ebf451e..d6cd163 100644 --- a/bot4cj/cjpm.toml +++ b/bot4cj/cjpm.toml @@ -21,6 +21,7 @@ spi4cj = "1.0.0" #lua4cj = { git = "https://gitcode.com/qingss0/lua4cj.git", branch="main" } lua4cj = "1.0.0" + "cangjie_tpc::matrix4cj" = "1.0.5" # 这种x86机器人开发板感觉不经常使用,我没用过,不知道里面的i2c调用是不是参数是一样的 [target.x86_64-unknown-linux-gnu] [target.x86_64-unknown-linux-gnu.dependencies] diff --git a/bot4cj/src/main.cj b/bot4cj/src/main.cj index aacb768..dfbda63 100644 --- a/bot4cj/src/main.cj +++ b/bot4cj/src/main.cj @@ -1,15 +1,23 @@ package bot4cj -import std.io.* -import std.fs.* + +/* + * 机器人脚本解析器 - 仓颉语言 + * + */ + +import std.argopt.* +import std.collection.* import std.env.* import std.time.* -import std.argopt.* import std.convert.* -import std.collection.* +import std.random.* +import std.io.* +import std.fs.* import stdx.encoding.json.* import bot4cj.hd.* import bot4cj.math.* import ctype4cj.* +import cangjie_tpc::matrix4cj.Matrix /* ---- 神经网络输出路径 ---- */ var NNS_OUT: Option = None @@ -17,10 +25,10 @@ main(args: Array): 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) /* ---- 运行一个测试分支 - 需要输入测试类型 ---- */ + ArgumentSpec.Long("help", ArgumentMode.NoValue), /* ---- 拉起帮助 ---- */ + ArgumentSpec.Short(r'i', ArgumentMode.RequiredValue), /* ---- 输入模型 ---- */ + ArgumentSpec.Short(r'o', ArgumentMode.RequiredValue), /* ---- 输出模型 ---- */ + ArgumentSpec.Long("test", ArgumentMode.RequiredValue) /* ---- 输入一个测试 ---- */ ] /* ---- 执行解析 ---- */ @@ -30,10 +38,10 @@ main(args: Array): Int64 { //--------------------------- /* 读取设置项 */ - /*---------------------------*/ - /*---------------------------*/ - /* 初始化神经网络 (init nn) - * TODO: 可以去掉内部初始化参数,使用矩阵式模板进行初始化 */ + /* + * 初始化神经网络 (init nn) + * TODO: 可以去掉内部初始化参数,使用矩阵式模板进行初始化 + */ //var nns: ArrayList = @@ -53,28 +61,27 @@ main(args: Array): Int64 { NNS_OUT = Option.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): 进行脚本引入(相对路径也可,从**环境变量**的路径中搜寻也可) */ + /* + * 缺省行为:从标准输入获取动作脚本 + * + * TODO(20260519-qing): 进行脚本引入(相对路径也可,从**环境变量**的路径中搜寻也可) + */ else { let consoleReader = getStdIn() let robot = RobotStruct(consoleReader) @@ -82,12 +89,11 @@ main(args: Array): Int64 { robot.active() robot.run() } - finally { - robot.deactive() - } - /* ---- NOTE(20260523-qing): 储存微调网络模型 - * ---- */ + /* 无论有没有成功激活,都进行清理 */ + finally { robot.deactive() } + + /* NOTE(20260523-qing): 储存微调网络模型 */ // let consoleReader = getStdIn() // run_action_drl(consoleReader, nns) // if (options.contains("o")) { @@ -98,8 +104,7 @@ main(args: Array): Int64 { return 0 } -/* -------- 打印帮助手册 - * -------- */ +/* -------- 打印帮助手册 -------- */ func print_help() { println("这是一个机器人动作脚本解析器。") @@ -112,13 +117,14 @@ func print_help() println("-o 设置输出的模型的路径") } -/* -------- 集成多种常规测试、临时测试 - * -------- */ +/* -------- 集成多种常规测试、临时测试 -------- */ func run_test(key: String) { - /* ----------- 神经网络与门测试 - * ----------- - * ----------- INFO: 我们使用随机生成的动态数据集,这样会导致一些后果 */ + /* + * 神经网络与门测试 + * + * INFO: 我们使用随机生成的动态数据集,这样会导致一些后果 + */ if (key == "nn") { let nn_t = ArrayList() nn_t.add(Nn1(2, 4, 0.05f32, Af.Leaky_ReLU(FP32(0.01)))) @@ -139,6 +145,35 @@ func run_test(key: String) { } } + else if (key == "nn1") { + + let m = Matrix(1, 2) + let t = Matrix(1, 2) + + //let random = Random() + 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.0f64 } else { 0.0f64 } + let t1 = if (a != b) { 1.0f64 } else { 0.0f64 } + m.set(0, 0, Float64(a)) + m.set(0, 1, Float64(b)) + t.set(0, 0, t0) + t.set(0, 1, t1) + + let nns = Nns() + nns.add(Nn2(2, 4, 0.01f64, Af.ReLU)) + nns.add(Nn2(4, 2, 0.01f64, Af.ReLU)) + let num = 200 + let prt_num = 10 + for (i in 0..num) { + let loss = nns.train(m, t, 1.0f64) + if (i % prt_num == prt_num-1) { + println("${i}\t: ${loss}") + } + } + } + else if (key == "cnn") { testCNN() diff --git a/bot4cj/src/math/Ini.cj b/bot4cj/src/math/Ini.cj index d62fcc6..e151697 100644 --- a/bot4cj/src/math/Ini.cj +++ b/bot4cj/src/math/Ini.cj @@ -21,6 +21,15 @@ public class Ini { } } + /* xavier正态初始化 - 返回数组 */ + public static func xavierNormalInit(fan_in: Int64, fan_out: Int64): Array> { + let seed = DateTime.now().nanosecond + let random = Random(UInt64(seed)) + let sigma = sqrt(2.0f64 / Float64(fan_in + fan_out)) /* 标准差 */ + var array = Array>(fan_in, repeat: Array(fan_out, repeat: random.nextGaussianFloat64(mean: 0.0f64, sigma: sigma))) + return array + } + /* -------- Glorot均匀初始化 -------- */ public static func xavierUniformInit(target: Matrix1, fan_in: Int64, fan_out: Int64) { let limit = sqrt(6.0f32 / Float32(fan_in + fan_out)) @@ -34,4 +43,13 @@ public class Ini { } } + + /* xavier正态初始化 - 返回数组 */ + public static func xavierUniformInit(fan_in: Int64, fan_out: Int64): Array> { + let limit = sqrt(6.0f64 / Float64(fan_in + fan_out)) + let seed = DateTime.now().nanosecond + let random = Random(UInt64(seed)) + var array = Array>(fan_in, repeat: Array(fan_out, repeat: (random.nextFloat64() * 2.0f64 - 1.0f64) * limit)) + return array + } } diff --git a/bot4cj/src/math/nn.cj b/bot4cj/src/math/nn.cj index 24384be..fc7f243 100644 --- a/bot4cj/src/math/nn.cj +++ b/bot4cj/src/math/nn.cj @@ -2,6 +2,8 @@ package bot4cj.math import std.fs.* import std.io.* import std.convert.* +import std.collection.* +import cangjie_tpc::matrix4cj.Matrix //foreign func Sigmoid_do(m: CPointer): CPointer //foreign func Sigmoid_dot(m: CPointer): CPointer //foreign func ReLU_do(m: CPointer): CPointer @@ -12,6 +14,153 @@ import std.convert.* //foreign func Tanh_dot(m: CPointer): CPointer + +public class Nns { + + let nns = ArrayList() + + public func add(layer: Nn2) { + nns.add(layer) + } + + /* 【神经网络训练】 + * description: 输入一组原始数据和教师数据,进行神经网络训练 + * @param nns: 输入神经网络结构 + * @param m: 输入一组原始数据 + * @param t: 输入一组教师数据 + * @param discount: 输入本次训练的折扣系数 + */ + public func train(m: Matrix, t: Matrix, discount: Float64) { + + /* ======== 这个对象用于暂时占位 ======== */ + var res = Option.None + + /* ======== 正向传播 ======== */ + for (idx in 0..nns.size) { + let x = if (idx == 0) { m } else { res.getOrThrow() } + res = nns[idx].forward(x) + } + + /* ======== 计算误差 ======== */ + var errs = res.getOrThrow().minus(t) + + /* ======== 反向传播 ======== */ + 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) + let r0 = res.getOrThrow().get(0, 0) + let loss0 = 0.5 * (t0 - r0) * (t0 - r0) + //let loss1 = 0.5 * (t1 - r1) * (t1 - r1) + //return (loss0 + loss1) / 2.0 + return loss0 + } +} + +/* + * 20260823版本的神经网络,使用matrix4cj库 + * TODO: 1.使用matrix4cj重写 + * 2.使用数组表示批次 + */ +public class Nn2 { + + /* ---- 形状 ---- */ + let inputno: Int64 + let outputno:Int64 + /* ---- 参数 ---- */ + var weights: Matrix /* (inputs, outputs) */ + var bias: Matrix /* (1, output) */ + /* ---- 学习 ---- */ + let lr: Float64 + let f: Af /* 激活函数 */ + /* ---- 缓存 ----*/ + var prev_input: Option = None + var prev_raw_output: Option = None + var prev_output: Option = None + var prev_weight_grad: Option = None + /* ---- 梯度 ----*/ + var weights_grad: Option = None + var bias_grad: Option = None + + /* -------- 带泄露的线性整流单元 -------- */ + private static func leaky_relu(M: Matrix, alpha: Float64) { + let row = M.getRowDimension() + let col = M.getColumnDimension() + let N = Matrix(row, col) + for (r in 0..row) { + for (c in 0..col) { + let x1 = M.get(r, c) + let x2 =if (x1 > 0.0f64) {x1} else{x1 * alpha} + N.set(r, c, x2) + } + } + return N + } + + /* -------- 带泄露的整流单元导数 -------- */ + private static func leaky_relu_dot(M: Matrix, alpha: Float64) { + let row = M.getRowDimension() + let col = M.getColumnDimension() + let N = Matrix(row, col) + for (r in 0..row) { + for (c in 0..col) { + let x1 = M.get(r, c) + let x2 =if (x1 > 0.0f64) { 1.0f64 } else{ alpha } + N.set(r, c, x2) + } + } + return N + } + /* ---- 初始化器 ---- */ + public init(i: Int64, o: Int64, lr: Float64, f: Af) { + inputno = i + outputno= o + this.lr = lr + this.f = f + weights = Matrix(Ini.xavierNormalInit(i, o)) + bias = Matrix(1, o) + } + + /* ---- 推理 ---- */ + public func forward(x: Matrix) { + prev_input = x + var r = x.times(weights) + r = r.plus(bias) + prev_raw_output = r + var r1 = leaky_relu(r, 0.01f64) + prev_output = r1 + return r1 + } + + /* ---- 反向传播 ---- */ + public func backward(e: Matrix) { + let fb = leaky_relu_dot(prev_raw_output.getOrThrow(), 0.01f64) + let first = e.arrayTimes(fb) + let mx = prev_input.getOrThrow().transpose() + weights_grad = mx.times(first) + bias_grad = first + let e_up = first.times(weights.transpose()) + return e_up + } + + /* -------- 更新权重值、偏爱因子 -------- + * NOTE: discount(折扣)是为了方便深度强化学习给出奖励值 */ + public func update(discount: Float64) { + weights = weights.minus(weights_grad.getOrThrow() * lr * discount) + bias = bias.minus(bias_grad.getOrThrow() * lr * discount) + } + +} + /* -------- 新版的神经网络,使用原生矩阵对象实现 -------- * TODO(20260517-qing): 增加batch批次处理,使用ArrayList * TODO*(20260519-qing): 将神经网络转换为MatrixObject矩阵式对象