专栏文章
使用卷积神经网络(CNN)强行通过 IOI 神题
算法·理论参与者 28已保存评论 34
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 33 条
- 当前快照
- 1 份
- 快照标识符
- @mm9fdyq1
- 此快照首次捕获于
- 2026/03/03 01:01 上周
- 此快照最后确认于
- 2026/03/10 01:01 15 小时前
使用卷积神经网络(CNN)强行通过 IOI 神题
前言
这是题目 P15429 [IOI 2013] Art Class 艺术分类 的整活版做法,你也可以在该题的题解区找到这篇文章的类似版本。
近几年 AI 技术爆发,AI 也成为了生活的一部分。在这个时代背景之下看到这个题一秒就会发现这是一个 卷积神经网络(CNN) 模板。但在 Online Judge 上跑 CNN 并不是一件简单的事情,不过为了整活(通过这题),我还是强行使用了 CNN。
由于测评机没有 PyTorch、TensorFlow 等环境,故需要手写神经网络,恰好之前写过一个神经网络模板类,可以前往 GitHub 查看,这个项目将神经网络打包成了模板,通过简单的模板参数设置即可实现各种类型的卷积神经网络,非常方便,助力完成本题,但是本题还需要针对云端测评环境做很多特殊优化,该模板类最终已经被改的面目全非了。
难点
时间限制并不是瓶颈,5 秒对于训练来讲是远远不够的,但是训练是在本地进行的,云端只需要推理, 秒绰绰有余。
真正的困难:
- 空间限制,64 MB 的空间限制难以存下很大的矩阵。
- 代码长度限制是最大的难点。并不是因为卷积神经网络的实现代码很长,而是因为需要将训练好的模型存储在代码里再提交评测,模型动辄百兆,但洛谷评测的代码长度限制貌似只有 50 KB 左右。需要在非常有限的空间中存下一个足以解决这个问题的模型是相当困难的。
- 样本数量限制。由于题目提供的样本数量极少,难以支撑神经网络的学习。要知道仅仅是解决手写数字识别问题的 MNIST 数据集都包含六万个训练样本,和本题相比,本题的样本数少的离谱。
神经网络结构设计
首先将图片拉伸成 ,虽然这使得其丢失了很多信息,但是再大了话生成的模型就大的存不下了。每个像素 个通道,因此输入层大小为:。输出层大小显然为 。隐藏层开设了两个,第一个隐藏层大小为 ,此时输入层到第一个隐藏层需要存储 个权重数据,再多就交不上去了。第二个隐藏层大小为 。激活函数使用 。每个神经元都有独立的 。训练采用梯度下降算法,计算使用
double。此时这个神经网络的基本功能就搞定了,接下来要开始特殊优化了。存储方式优化
GitHub 里的项目直接采用二进制文件存储模型数据,在本题中,我先是尝试把模型写成代码,例如:
CPPbiases[2]=std::vector<std::vector<float>>({{-0.408,-0.384,0.177,0.186}});
但这样还需要存储逗号,括号,实在是太浪费空间了,因此我将数据存储成字符串放在代码里,然后再用代码读取。由于输出的小数位数是固定的且没有数的绝对值大于等于 ,两个数之间无需空格隔开,例如:
CPP6.282 -3.141 0.618
存储为:
CPP6282-31410618
又发现字符集只有 和负号,共 个,只需要 4 bit 就能存储,一个字符是 8 bit,可以优化,但这个实现有点麻烦,如果直接把两个字符组合成一个是不可行的,因为 但 ASCII 可见字符数量只有 个,不可见字符没法存在代码里,因此我让 AI 帮我实现了这部分代码,直接当黑箱用了。
CPPstd::string zip(std::string s) {
std::string b;
for (size_t i = 0; i < s.size(); i += 2) {
int v1 = (s[i] == '-') ? 10 : s[i] - '0';
int v2 = (i + 1 < s.size()) ? ((s[i+1] == '-') ? 10 : s[i+1] - '0') : 0;
b += (char)((v1 << 4) | v2);
}
const char* t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string r;
for (size_t i = 0; i < b.size(); i += 3) {
unsigned int v = (unsigned char)b[i] << 16;
if (i+1 < b.size()) v |= (unsigned char)b[i+1] << 8;
if (i+2 < b.size()) v |= (unsigned char)b[i+2];
r += t[(v >> 18) & 63];
r += t[(v >> 12) & 63];
r += (i+1 < b.size()) ? t[(v >> 6) & 63] : '=';
r += (i+2 < b.size()) ? t[v & 63] : '=';
}
return r + (s.size() % 2 ? '1' : '0');
}
std::string unzip(std::string s) {
if (s.empty()) return "";
bool odd = (s.back() == '1');
s.pop_back();
auto idx = [](char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
return (c == '+') ? 62 : 63;
};
std::string b;
for (size_t i = 0; i < s.size(); i += 4) {
unsigned int v = 0; int cnt = 0;
for (int j = 0; j < 4; ++j) {
v <<= 6;
if (i+j < s.size() && s[i+j] != '=') { v |= idx(s[i+j]); cnt++; }
}
if (cnt > 0) b += (char)((v >> 16) & 0xFF);
if (cnt > 1) b += (char)((v >> 8) & 0xFF);
if (cnt > 2) b += (char)(v & 0xFF);
}
std::string res;
for (unsigned char c : b) {
int v1 = c >> 4, v2 = c & 0xF;
res += (v1 == 10) ? '-' : (char)('0' + v1);
res += (v2 == 10) ? '-' : (char)('0' + v2);
}
if (odd && !res.empty()) res.pop_back();
return res;
}
此时经历了两次压缩,体积大幅减小,使得使用 CNN 通过此题成为可能。
但是,用样例数据训练了半天还是最多只能拿五六十分,关键在于样本太少了,每个类型只有区区十个左右,神经网络不能完全学会,所以我又想办法造了几组,但是工作量过大,几组之后就造不下去了,于是决定开挂,搜了一下 IOI 原题数据,从里面借了一部分过来,没有全用,借了一小部分,目前我也找不到完整的,只能找到一些零碎的。然后再结合上自己造的就让神经网络学会了区分这四种图片,愉快的通过了此题!
实现代码
Matrix
这个模板也是我自己实现的,可以前往 GitHub 查看,这是个数学模板集合,里面还有一个超快的多项式模板,同时支持 FFT、NTT,还支持多项式 、多项式开根等操作。
不用线性代数的矩阵也可实现 CNN,但 CNN 中很多东西本质就是矩阵乘法,使用矩阵模板实现起来会更加方便。
CPP#ifndef LINEARALGEBRA_H
#define LINEARALGEBRA_H
#include <vector>
#include <cassert>
#include <memory>
#include <utility>
#include <algorithm>
#include <iostream>
namespace LinearAlgebra {
template<typename Type>
class Matrix {
private:
size_t n, m;
std::unique_ptr<Type[]> data;
public:
Matrix(): n(0), m(0), data(nullptr) {}
Matrix(size_t __n, size_t __m, const Type& x = Type(0)): n(__n), m(__m), data(std::make_unique<Type[]>(__n * __m)) { std::fill_n(data.get(), __n * __m, x); }
Matrix(Matrix&& o) noexcept : n(o.n), m(o.m), data(std::move(o.data)) { o.n = o.m = 0; }
Matrix& operator=(Matrix&& o) noexcept {
if (this != &o) { n = o.n; m = o.m; data = std::move(o.data); o.n = o.m = 0; }
return *this;
}
Matrix& resize(size_t __n, size_t __m, const Type& x = Type(0)) {
n = __n, m = __m; data = std::make_unique<Type[]>(__n * __m);
std::fill_n(data.get(), __n * __m, x);
return *this;
}
Matrix(const std::vector<std::vector<Type>>& x): n(x.size()), m(x.front().size()), data(std::make_unique<Type[]>(x.size() * x.front().size())) {
for (size_t i = 0; i < n; ++i)
for (size_t j = 0; j < m; ++j)
data[i * m + j] = x[i][j];
}
Matrix(const Matrix& o): n(o.n), m(o.m), data(nullptr) {
if (o.data == nullptr || n == 0 || m == 0) return;
data = std::make_unique<Type[]>(n * m);
for (Type *ptr = o.data.get(), *end = o.data.get() + n * m, *ptrr = data.get(); ptr != end; ++ptr, ++ptrr) *ptrr = *ptr;
// std::copy(o.data.get(), o.data.get() + n * m, data.get());
}
Type* operator[](size_t row) { return data.get() + row * m; }
const Type* operator[](size_t row) const { return data.get() + row * m; }
Matrix& operator=(const Matrix& o) {
if (this != &o) {
if (o.data == nullptr || o.n == 0 || o.m == 0) {
n = m = 0; data = nullptr;
return *this;
}
n = o.n; m = o.m; data = std::make_unique<Type[]>(n * m);
for (Type *ptr = o.data.get(), *end = o.data.get() + n * m, *ptrr = data.get(); ptr != end; ++ptr, ++ptrr) *ptrr = *ptr;
// std::copy(o.data.get(), o.data.get() + n * m, data.get());
}
return *this;
}
Matrix& operator=(const std::vector<std::vector<Type>>& o) {
n = o.size(); m = o.front().size(); data = std::make_unique<Type[]>(n * m);
for (size_t i = 0; i < n; ++i)
for (size_t j = 0; j < m; ++j)
data[i * m + j] = o[i][j];
return *this;
}
size_t N() const { return n; }
size_t M() const { return m; }
Type& operator () (size_t row, size_t col) { return data[row * m + col]; }
const Type& operator () (size_t row, size_t col) const { return data[row * m + col]; }
Matrix transpose() const {
Matrix res(m, n);
Type* src_row = data.get();
for (size_t i = 0; i < n; ++i, src_row += m) {
Type* dst = res.data.get() + i;
Type* src = src_row;
for (size_t j = 0; j < m; ++j, ++src, dst += n) {
*dst = *src;
}
}
return res; // RVO
}
Matrix& transposeSelf() {
Matrix res(m, n);
Type* src_row = data.get();
for (size_t i = 0; i < n; ++i, src_row += m) {
Type* dst = res.data.get() + i;
Type* src = src_row;
for (size_t j = 0; j < m; ++j, ++src, dst += n) {
*dst = *src;
}
}
return *this = std::move(res);
}
Matrix& operator *= (const Matrix& o) {
Matrix res(n, o.m);
Type *ptr = data.get();
for (size_t i = 0; i < n; ++i) {
Type *ptro = o.data.get();
for (size_t k = 0; k < m; ++k, ++ptr) {
Type tmp = *ptr;
Type *ptrr = res.data.get() + i * o.m;
for (size_t j = 0; j < o.m; ++j, ++ptro, ++ptrr) {
*ptrr += tmp * *ptro;
}
}
}
return *this = std::move(res);
}
Matrix operator * (const Matrix& o) const {
Matrix res(n, o.m);
Type *ptr = data.get();
for (size_t i = 0; i < n; ++i) {
Type *ptro = o.data.get();
for (size_t k = 0; k < m; ++k, ++ptr) {
Type tmp = *ptr;
Type *ptrr = res.data.get() + i * o.m;
for (size_t j = 0; j < o.m; ++j, ++ptro, ++ptrr) {
*ptrr += tmp * *ptro;
}
}
}
return res; // RVO
}
Matrix& operator += (const Matrix& o) {
for (Type *ptr = data.get(), *end = data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro) *ptr += *ptro;
return *this;
}
Matrix operator + (const Matrix& o) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro) *ptr += *ptro;
return res; // RVO
}
Matrix& operator -= (const Matrix& o) {
for (Type *ptr = data.get(), *end = data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro) *ptr -= *ptro;
return *this;
}
Matrix operator - (const Matrix& o) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro) *ptr -= *ptro;
return res; // RVO
}
Matrix& operator *= (const Type& o) {
for (Type *ptr = data.get(), *end = data.get() + n * m; ptr != end; ++ptr) *ptr *= o;
return *this;
}
Matrix operator * (const Type& o) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m; ptr != end; ++ptr) *ptr *= o;
return res; // RVO
}
Matrix& operator += (const Type &o) {
for (Type *ptr = data.get(), *end = data.get() + n * m; ptr != end; ++ptr) *ptr += o;
return *this;
}
Matrix operator + (const Type &o) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m; ptr != end; ++ptr) *ptr += o;
return res; // RVO
}
Matrix& operator -= (const Type &o) {
for (Type *ptr = data.get(), *end = data.get() + n * m; ptr != end; ++ptr) *ptr -= o;
return *this;
}
Matrix operator - (const Type &o) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m; ptr != end; ++ptr) *ptr -= o;
return res; // RVO
}
bool operator == (const Matrix& o) const {
if (n != o.n || m != o.m) return false;
if (data == o.data) return true;
if (data == nullptr || o.data == nullptr) return false;
for (Type *ptr = data.get(), *end = data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro)
if (*ptr != *ptro) return false;
return true;
}
bool operator != (const Matrix& o) const {
if (n != o.n || m != o.m) return true;
if (data == o.data) return false;
if (data == nullptr || o.data == nullptr) return true;
for (Type *ptr = data.get(), *end = data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro)
if (*ptr != *ptro) return true;
return false;
}
Matrix& applyFunctionSelf(Type (*func)(Type)) { for (Type *ptr = data.get(), *end = data.get() + n * m; ptr != end; ++ptr) *ptr = func(*ptr); return *this; }
Matrix& applyFunctionSelf(Type (*func)(const Type&)) { for (Type *ptr = data.get(), *end = data.get() + n * m; ptr != end; ++ptr) *ptr = func(*ptr); return *this; }
Matrix applyFunction(Type (*func)(Type)) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m; ptr != end; ++ptr) *ptr = func(*ptr);
return res; // RVO
}
Matrix applyFunction(Type (*func)(const Type&)) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m; ptr != end; ++ptr) *ptr = func(*ptr);
return res; // RVO
}
Matrix operator%(const Matrix& o) const {
Matrix res(n, m);
for (Type *ptr = data.get(), *end = data.get() + n * m, *ptro = o.data.get(), *ptrr = res.data.get(); ptr != end; ++ptr, ++ptro, ++ptrr) *ptrr = *ptr * *ptro;
return res; // RVO
}
Matrix& operator%=(const Matrix& o) {
for (Type *ptr = data.get(), *end = data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro) *ptr *= *ptro;
return *this;
}
friend std::ostream& operator<<(std::ostream& os, const Matrix& p) {
for (size_t i = 0; i < p.n; ++i) {
for (size_t j = 0; j < p.m; ++j) {
os << p(i, j) << " \n"[j == p.m - 1];
}
if (i != p.n - 1) os << "\n";
}
return os;
}
};
}
#endif
神经网络模板类
CPP#include "LinearAlgebra.hpp"
#include <random>
#include <iostream>
#include <utility>
#include <vector>
#include <string>
#include <cstdio>
#include <cassert>
#include <cmath>
#include <ctime>
#include <bits/stdc++.h>
std::string zip(std::string s) {
std::string b;
for (size_t i = 0; i < s.size(); i += 2) {
int v1 = (s[i] == '-') ? 10 : s[i] - '0';
int v2 = (i + 1 < s.size()) ? ((s[i+1] == '-') ? 10 : s[i+1] - '0') : 0;
b += (char)((v1 << 4) | v2);
}
const char* t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string r;
for (size_t i = 0; i < b.size(); i += 3) {
unsigned int v = (unsigned char)b[i] << 16;
if (i+1 < b.size()) v |= (unsigned char)b[i+1] << 8;
if (i+2 < b.size()) v |= (unsigned char)b[i+2];
r += t[(v >> 18) & 63];
r += t[(v >> 12) & 63];
r += (i+1 < b.size()) ? t[(v >> 6) & 63] : '=';
r += (i+2 < b.size()) ? t[v & 63] : '=';
}
return r + (s.size() % 2 ? '1' : '0');
}
std::string unzip(std::string s) {
if (s.empty()) return "";
bool odd = (s.back() == '1');
s.pop_back();
auto idx = [](char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
return (c == '+') ? 62 : 63;
};
std::string b;
for (size_t i = 0; i < s.size(); i += 4) {
unsigned int v = 0; int cnt = 0;
for (int j = 0; j < 4; ++j) {
v <<= 6;
if (i+j < s.size() && s[i+j] != '=') { v |= idx(s[i+j]); cnt++; }
}
if (cnt > 0) b += (char)((v >> 16) & 0xFF);
if (cnt > 1) b += (char)((v >> 8) & 0xFF);
if (cnt > 2) b += (char)(v & 0xFF);
}
std::string res;
for (unsigned char c : b) {
int v1 = c >> 4, v2 = c & 0xF;
res += (v1 == 10) ? '-' : (char)('0' + v1);
res += (v2 == 10) ? '-' : (char)('0' + v2);
}
if (odd && !res.empty()) res.pop_back();
return res;
}
namespace QNet {
template<typename T>
T randomReal(const T& l, const T& r) {
static std::random_device rd;
static std::mt19937 gen(rd());
std::uniform_real_distribution<T> dis(l, r);
return dis(gen);
}
template<typename T>
inline T sigmoid(const T& x) {
return 1.0 / (1.0 + std::exp(-x));
}
template<typename T>
inline T dSigmoid(const T& x) {
const T E = std::exp(-x);
const T t = 1 + E;
return E / (t * t);
}
template<typename T = float, typename Mat = LinearAlgebra::Matrix<T>, T (*actFunc)(const T&) = sigmoid<T>, T (*dActFunc)(const T&) = dSigmoid<T>, const bool haveBiases = true, const bool haveOutputActivation = true>
class Net {
private:
std::vector<Mat> weights, biases;
std::vector<std::pair<Mat, Mat>> __forward(Mat input) const {
std::vector<std::pair<Mat, Mat>> res;
for (size_t i = 0; i < weights.size() - 1; i++) {
input *= weights[i];
if (haveBiases) input += biases[i];
res.push_back(std::make_pair(input, Mat()));
input.applyFunctionSelf(actFunc);
res.back().second = input;
}
input *= weights.back();
if (haveBiases) input += biases.back();
res.push_back(std::make_pair(input, Mat()));
if (haveOutputActivation) {
input.applyFunctionSelf(actFunc);
res.back().second = input;
}
return res;
}
public:
size_t getInputSize() const { return weights.front().N(); }
size_t getOutputSize() const { return weights.back().M(); }
std::vector<Mat>& getWeights() { return weights; }
std::vector<Mat>& getBiases() { return biases; }
const std::vector<Mat>& getWeights() const { return weights; }
const std::vector<Mat>& getBiases() const { return biases; }
Net(size_t inputSize, const std::vector<size_t>& Layers): weights(Layers.size()), biases(0) {
if (haveBiases) biases.resize(Layers.size());
weights.front().resize(inputSize, Layers.front());
if (haveBiases) biases.front().resize(1, Layers.front());
for (size_t i = 1; i < Layers.size(); ++i) {
weights[i].resize(Layers[i - 1], Layers[i]);
if (haveBiases) biases[i].resize(1, Layers[i]);
}
}
Mat forward(Mat input) const {
for (size_t i = 0; i + 1 < weights.size(); ++i) {
input *= weights[i];
if (haveBiases) input += biases[i];
input.applyFunctionSelf(actFunc);
}
input *= weights.back();
if (haveBiases) input += biases.back();
if (haveOutputActivation) input.applyFunctionSelf(actFunc);
return input;
}
void init(const T& l = -0.5, const T& r = 0.5, T (*randomFunc)(const T&, const T&) = randomReal<T>) {
for (auto &x: weights)
for (size_t i = 0; i < x.N(); ++i)
for (size_t j = 0; j < x.M(); ++j)
x(i, j) = randomFunc(l, r);
if (!haveBiases) return;
for (auto &x: biases)
for (size_t i = 0; i < x.N(); ++i)
for (size_t j = 0; j < x.M(); ++j)
x(i, j) = randomFunc(l, r);
}
void save(std::ostream& out) {
std::string res;
for (auto& x : weights) {
for (int i = 0; i < (int) x.N(); i++) {
for (int j = 0; j < (int) x.M(); j++) {
assert(x(i, j) < 10);
if (x(i, j) < 0) res.push_back('-'), x(i, j) = -x(i, j);
int d = x(i, j);
res.push_back(d % 10 + '0');
d = x(i, j) * 10; res.push_back(d % 10 + '0');
d = x(i, j) * 100; res.push_back(d % 10 + '0');
d = x(i, j) * 1000; res.push_back(d % 10 + '0');
d = x(i, j) * 10000; res.push_back(d % 10 + '0');
d = x(i, j) * 100000; res.push_back(d % 10 + '0');
}
}
}
for (auto& x : biases) {
for (int i = 0; i < (int) x.N(); i++) {
for (int j = 0; j < (int) x.M(); j++) {
assert(x(i, j) < 10);
if (x(i, j) < 0) res.push_back('-'), x(i, j) = -x(i, j);
int d = x(i, j);
res.push_back(d % 10 + '0');
d = x(i, j) * 10; res.push_back(d % 10 + '0');
d = x(i, j) * 100; res.push_back(d % 10 + '0');
d = x(i, j) * 1000; res.push_back(d % 10 + '0');
d = x(i, j) * 10000; res.push_back(d % 10 + '0');
d = x(i, j) * 100000; res.push_back(d % 10 + '0');
}
}
}
out << zip(res);
}
void open() {
std::string s = //省略了模型,最终版本的代码里有;
s = unzip(s);
reverse(s.begin(), s.end());
for (auto& x : weights) {
for (int i = 0; i < (int) x.N(); i++) {
for (int j = 0; j < (int) x.M(); j++) {
bool f = false;
if (s.back() == '-') s.pop_back(), f = true;
x(i, j) = s.back() - '0'; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 10; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 100; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 1000; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 10000; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 100000; s.pop_back();
if (f) x(i, j) = -x(i, j);
}
}
}
for (auto& x : biases) {
for (int i = 0; i < (int) x.N(); i++) {
for (int j = 0; j < (int) x.M(); j++) {
bool f = false;
if (s.back() == '-') s.pop_back(), f = true;
x(i, j) = s.back() - '0'; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 10; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 100; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 1000; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 10000; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 100000; s.pop_back();
if (f) x(i, j) = -x(i, j);
}
}
}
}
void train(const std::vector<Mat>& inputs, const std::vector<Mat>& targets, const size_t& epochs, const T& lRate, const size_t& len = 0, std::ostream& os = std::cout) {
std::vector<Mat> errors(weights.size());
clock_t start = clock();
clock_t st = clock();
for (size_t i = 0; i < errors.size(); i++) errors[i].resize(1, weights[i].M());
for (size_t epoch = 0; epoch < epochs; ++epoch) {
if (len > 0 && epoch % len == 0) {
os << "Epoch: " << epoch << " started..." << std::endl;
start = clock();
}
for (size_t id = 0; id < inputs.size(); id++) {
auto output = __forward(inputs[id]);
if (haveOutputActivation) {
errors.back() = (output.back().second - targets[id]) % output.back().first.applyFunction(dActFunc);
} else errors.back() = output.back().first - targets[id];
for (size_t i = weights.size() - 1; i > 0; i--) {
errors[i - 1] = errors[i] * weights[i].transpose();
errors[i - 1] %= output[i - 1].first.applyFunction(dActFunc);
if (haveBiases) biases[i] -= errors[i] * lRate;
for (size_t k = 0; k < weights[i].N(); k++)
for (size_t j = 0; j < weights[i].M(); j++)
weights[i](k, j) -= lRate * errors[i](0, j) * output[i - 1].second(0, k);
}
if (haveBiases) biases.front() -= errors.front() * lRate;
for (size_t j = 0; j < weights.front().N(); j++)
for (size_t i = 0; i < weights.front().M(); i++)
weights.front()(j, i) -= lRate * errors.front()(0, i) * inputs[id](0, j);
}
if (len > 0 && (epoch + 1) % len == 0) {
os << "Epoch: " << epoch << " completed." << std::endl;
os << "Time elapsed: " << double(clock() - start) / CLOCKS_PER_SEC << " seconds." << std::endl;
const double dt = double(clock() - start) / CLOCKS_PER_SEC / len * 1000;
os << dt << " ms per epoch, Predictly Sum: " << epochs * dt / 1000 << " s, Actually sum: " << double(clock() - st) / CLOCKS_PER_SEC << ", Need: " << (epochs - epoch - 1) * dt / 1000 << " s." << std::endl;
os << std::endl;
}
}
}
};
}
用于训练的代码
CPP#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include "QNet.hpp"
using namespace std;
using Mat = LinearAlgebra::Matrix<double>;
constexpr int N = 15;
#define os cout
vector<vector<int>> rsz(const vector<vector<int>>& a) {
vector<vector<int>> res(N, vector<int>(N));
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
int x = i * (a.size() - 1) / (N - 1);
int y = j * (a.front().size() - 1) / (N - 1);
x = min(x, (int) a.size() - 1);
y = min(y, (int) a.front().size() - 1);
res[i][j] = a[x][y];
}
}
return res;
}
void loadData(const string& Path, vector<Mat>& inputs, vector<Mat>& targets) {
ifstream fin(Path);
int id, n, m;
while (fin >> id) {
--id;
fin >> n >> m;
Mat ans(1, 4, 0), in(1, N * N * 3, 0); ans(0, id) = 1;
vector<vector<int>> r(n, vector<int>(m)), g(n, vector<int>(m)), b(n, vector<int>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
static char ch;
fin >> r[i][j] >> g[i][j] >> b[i][j] >> ch;
}
}
r = rsz(r), g = rsz(g), b = rsz(b);
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
in(0, i * N + j) = (float) r[i][j] / 255;
in(0, N * N + i * N + j) = (float) g[i][j] / 255;
in(0, N * N * 2 + i * N + j) = (float) b[i][j] / 255;
}
}
inputs.push_back(in);
targets.push_back(ans);
}
fin.close();
}
int main() {
QNet::Net<double, Mat, QNet::sigmoid, QNet::dSigmoid, true, true> nn(N * N * 3, {10, 138, 4});
os << "Initializing ANN..." << std::endl;
// nn.init();
nn.open();
vector<Mat> inputs, targets;
os << "Loading training data..." << std::endl;
loadData("..\\trainData.txt", inputs, targets);
os << "Training..." << std::endl;
// nn.train(inputs, targets, 50000, 1e-3, 500, os);
// nn.train(inputs, targets, 50000, 5e-4, 500, os);
// nn.train(inputs, targets, 50000, 1e-4, 500, os);
// nn.train(inputs, targets, 10000, 3e-5, 500, os);
// nn.train(inputs, targets, 10000, 1e-5, 500, os);
nn.train(inputs, targets, 10000, 3e-6, 500, os);
nn.train(inputs, targets, 10000, 1e-6, 500, os);
nn.train(inputs, targets, 10000, 3e-7, 500, os);
ofstream output("text.txt");
nn.save(output);
os << "Training completed." << endl;
return 0;
}
最终提交的代码(带有模型,比较长)
为了减小代码长度,我删除了大部分缩进。
CPP#include <bits/stdc++.h>
#ifndef LINEARALGEBRA_H
#define LINEARALGEBRA_H
namespace LinearAlgebra {
template<typename Type>
class Matrix {
private:
size_t n, m;
std::unique_ptr<Type[]> data;
public:
Matrix(): n(0), m(0), data(nullptr) {}
Matrix(size_t __n, size_t __m, const Type& x = Type(0)): n(__n), m(__m), data(std::make_unique<Type[]>(__n * __m)) { std::fill_n(data.get(), __n * __m, x); }
Matrix(Matrix&& o) noexcept : n(o.n), m(o.m), data(std::move(o.data)) { o.n = o.m = 0; }
Matrix& operator=(Matrix&& o) noexcept {
if (this != &o) { n = o.n; m = o.m; data = std::move(o.data); o.n = o.m = 0; }
return *this;
}
Matrix& resize(size_t __n, size_t __m, const Type& x = Type(0)) {
n = __n, m = __m; data = std::make_unique<Type[]>(__n * __m);
std::fill_n(data.get(), __n * __m, x);
return *this;
}
Matrix(const std::vector<std::vector<Type>>& x): n(x.size()), m(x.front().size()), data(std::make_unique<Type[]>(x.size() * x.front().size())) {
for (size_t i = 0; i < n; ++i)
for (size_t j = 0; j < m; ++j)
data[i * m + j] = x[i][j];
}
Matrix(const Matrix& o): n(o.n), m(o.m), data(nullptr) {
if (o.data == nullptr || n == 0 || m == 0) return;
data = std::make_unique<Type[]>(n * m);
for (Type *ptr = o.data.get(), *end = o.data.get() + n * m, *ptrr = data.get(); ptr != end; ++ptr, ++ptrr) *ptrr = *ptr;
}
Type* operator[](size_t row) { return data.get() + row * m; }
const Type* operator[](size_t row) const { return data.get() + row * m; }
Matrix& operator=(const Matrix& o) {
if (this != &o) {
if (o.data == nullptr || o.n == 0 || o.m == 0) {
n = m = 0; data = nullptr;
return *this;
}
n = o.n; m = o.m; data = std::make_unique<Type[]>(n * m);
for (Type *ptr = o.data.get(), *end = o.data.get() + n * m, *ptrr = data.get(); ptr != end; ++ptr, ++ptrr) *ptrr = *ptr;
}
return *this;
}
Matrix& operator=(const std::vector<std::vector<Type>>& o) {
n = o.size(); m = o.front().size(); data = std::make_unique<Type[]>(n * m);
for (size_t i = 0; i < n; ++i)
for (size_t j = 0; j < m; ++j)
data[i * m + j] = o[i][j];
return *this;
}
size_t N() const { return n; }
size_t M() const { return m; }
Type& operator () (size_t row, size_t col) { return data[row * m + col]; }
const Type& operator () (size_t row, size_t col) const { return data[row * m + col]; }
Matrix transpose() const {
Matrix res(m, n);
Type* src_row = data.get();
for (size_t i = 0; i < n; ++i, src_row += m) {
Type* dst = res.data.get() + i;
Type* src = src_row;
for (size_t j = 0; j < m; ++j, ++src, dst += n) {
*dst = *src;
}
}
return res;
}
Matrix& transposeSelf() {
Matrix res(m, n);
Type* src_row = data.get();
for (size_t i = 0; i < n; ++i, src_row += m) {
Type* dst = res.data.get() + i;
Type* src = src_row;
for (size_t j = 0; j < m; ++j, ++src, dst += n) {
*dst = *src;
}
}
return *this = std::move(res);
}
Matrix& operator *= (const Matrix& o) {
Matrix res(n, o.m);
Type *ptr = data.get();
for (size_t i = 0; i < n; ++i) {
Type *ptro = o.data.get();
for (size_t k = 0; k < m; ++k, ++ptr) {
Type tmp = *ptr;
Type *ptrr = res.data.get() + i * o.m;
for (size_t j = 0; j < o.m; ++j, ++ptro, ++ptrr) {
*ptrr += tmp * *ptro;
}
}
}
return *this = std::move(res);
}
Matrix operator * (const Matrix& o) const {
Matrix res(n, o.m);
Type *ptr = data.get();
for (size_t i = 0; i < n; ++i) {
Type *ptro = o.data.get();
for (size_t k = 0; k < m; ++k, ++ptr) {
Type tmp = *ptr;
Type *ptrr = res.data.get() + i * o.m;
for (size_t j = 0; j < o.m; ++j, ++ptro, ++ptrr) {
*ptrr += tmp * *ptro;
}
}
}
return res; // RVO
}
Matrix& operator += (const Matrix& o) {
for (Type *ptr = data.get(), *end = data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro) *ptr += *ptro;
return *this;
}
Matrix operator + (const Matrix& o) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro) *ptr += *ptro;
return res; // RVO
}
Matrix& operator -= (const Matrix& o) {
for (Type *ptr = data.get(), *end = data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro) *ptr -= *ptro;
return *this;
}
Matrix operator - (const Matrix& o) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro) *ptr -= *ptro;
return res; // RVO
}
Matrix& operator *= (const Type& o) {
for (Type *ptr = data.get(), *end = data.get() + n * m; ptr != end; ++ptr) *ptr *= o;
return *this;
}
Matrix operator * (const Type& o) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m; ptr != end; ++ptr) *ptr *= o;
return res; // RVO
}
Matrix& operator += (const Type &o) {
for (Type *ptr = data.get(), *end = data.get() + n * m; ptr != end; ++ptr) *ptr += o;
return *this;
}
Matrix operator + (const Type &o) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m; ptr != end; ++ptr) *ptr += o;
return res; // RVO
}
Matrix& operator -= (const Type &o) {
for (Type *ptr = data.get(), *end = data.get() + n * m; ptr != end; ++ptr) *ptr -= o;
return *this;
}
Matrix operator - (const Type &o) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m; ptr != end; ++ptr) *ptr -= o;
return res; // RVO
}
bool operator == (const Matrix& o) const {
if (n != o.n || m != o.m) return false;
if (data == o.data) return true;
if (data == nullptr || o.data == nullptr) return false;
for (Type *ptr = data.get(), *end = data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro)
if (*ptr != *ptro) return false;
return true;
}
bool operator != (const Matrix& o) const {
if (n != o.n || m != o.m) return true;
if (data == o.data) return false;
if (data == nullptr || o.data == nullptr) return true;
for (Type *ptr = data.get(), *end = data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro)
if (*ptr != *ptro) return true;
return false;
}
Matrix& applyFunctionSelf(Type (*func)(Type)) { for (Type *ptr = data.get(), *end = data.get() + n * m; ptr != end; ++ptr) *ptr = func(*ptr); return *this; }
Matrix& applyFunctionSelf(Type (*func)(const Type&)) { for (Type *ptr = data.get(), *end = data.get() + n * m; ptr != end; ++ptr) *ptr = func(*ptr); return *this; }
Matrix applyFunction(Type (*func)(Type)) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m; ptr != end; ++ptr) *ptr = func(*ptr);
return res;
}
Matrix applyFunction(Type (*func)(const Type&)) const {
Matrix res(*this);
for (Type *ptr = res.data.get(), *end = res.data.get() + n * m; ptr != end; ++ptr) *ptr = func(*ptr);
return res;
}
Matrix operator%(const Matrix& o) const {
Matrix res(n, m);
for (Type *ptr = data.get(), *end = data.get() + n * m, *ptro = o.data.get(), *ptrr = res.data.get(); ptr != end; ++ptr, ++ptro, ++ptrr) *ptrr = *ptr * *ptro;
return res;
}
Matrix& operator%=(const Matrix& o) {
for (Type *ptr = data.get(), *end = data.get() + n * m, *ptro = o.data.get(); ptr != end; ++ptr, ++ptro) *ptr *= *ptro;
return *this;
}
friend std::ostream& operator<<(std::ostream& os, const Matrix& p) {
for (size_t i = 0; i < p.n; ++i) {
for (size_t j = 0; j < p.m; ++j) {
os << p(i, j) << " \n"[j == p.m - 1];
}
if (i != p.n - 1) os << "\n";
}
return os;
}
};
}
#endif
namespace QNet {
template<typename T>
inline T sigmoid(const T& x) {
return 1.0 / (1.0 + std::exp(-x));
}
template<typename T>
inline T dSigmoid(const T& x) {
const T E = std::exp(-x);
const T t = 1 + E;
return E / (t * t);
}
std::string unzip(std::string s) {
if (s.empty()) return "";
bool odd = (s.back() == '1');
s.pop_back();
auto idx = [](char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
return (c == '+') ? 62 : 63;
};
std::string b;
for (size_t i = 0; i < s.size(); i += 4) {
unsigned int v = 0; int cnt = 0;
for (int j = 0; j < 4; ++j) {
v <<= 6;
if (i+j < s.size() && s[i+j] != '=') { v |= idx(s[i+j]); cnt++; }
}
if (cnt > 0) b += (char)((v >> 16) & 0xFF);
if (cnt > 1) b += (char)((v >> 8) & 0xFF);
if (cnt > 2) b += (char)(v & 0xFF);
}
std::string res;
for (unsigned char c : b) {
int v1 = c >> 4, v2 = c & 0xF;
res += (v1 == 10) ? '-' : (char)('0' + v1);
res += (v2 == 10) ? '-' : (char)('0' + v2);
}
if (odd && !res.empty()) res.pop_back();
return res;
}
template<typename T = float, typename Mat = LinearAlgebra::Matrix<T>, T (*actFunc)(const T&) = sigmoid<T>, T (*dActFunc)(const T&) = dSigmoid<T>, const bool haveBiases = true, const bool haveOutputActivation = true>
class Net {
private:
std::vector<Mat> weights, biases;
public:
Net(size_t inputSize, const std::vector<size_t>& Layers): weights(Layers.size()), biases(0) {
if (haveBiases) biases.resize(Layers.size());
weights.front().resize(inputSize, Layers.front());
if (haveBiases) biases.front().resize(1, Layers.front());
for (size_t i = 1; i < Layers.size(); ++i) {
weights[i].resize(Layers[i - 1], Layers[i]);
if (haveBiases) biases[i].resize(1, Layers[i]);
}
std::string s = "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";
s = unzip(s);
reverse(s.begin(), s.end());
for (auto& x : weights) {
for (int i = 0; i < (int) x.N(); i++) {
for (int j = 0; j < (int) x.M(); j++) {
bool f = false;
if (s.back() == '-') s.pop_back(), f = true;
x(i, j) = s.back() - '0'; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 10; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 100; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 1000; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 10000; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 100000; s.pop_back();
if (f) x(i, j) = -x(i, j);
}
}
}
for (auto& x : biases) {
for (int i = 0; i < (int) x.N(); i++) {
for (int j = 0; j < (int) x.M(); j++) {
bool f = false;
if (s.back() == '-') s.pop_back(), f = true;
x(i, j) = s.back() - '0'; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 10; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 100; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 1000; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 10000; s.pop_back();
x(i, j) += (double) (s.back() - '0') / 100000; s.pop_back();
if (f) x(i, j) = -x(i, j);
}
}
}
}
Mat forward(Mat input) const {
for (size_t i = 0; i + 1 < weights.size(); ++i) {
input *= weights[i];
if (haveBiases) input += biases[i];
input.applyFunctionSelf(actFunc);
}
input *= weights.back();
if (haveBiases) input += biases.back();
if (haveOutputActivation) input.applyFunctionSelf(actFunc);
return input;
}
};
}
using namespace std;
using Mat = LinearAlgebra::Matrix<double>;
constexpr int N = 15;
#define os cout
vector<vector<int>> rsz(const vector<vector<int>>& a) {
vector<vector<int>> res(N, vector<int>(N));
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
int x = i * (a.size() - 1) / (N - 1);
int y = j * (a.front().size() - 1) / (N - 1);
x = min(x, (int) a.size() - 1);
y = min(y, (int) a.front().size() - 1);
res[i][j] = a[x][y];
}
}
return res;
}
int predict(const Mat& output) {
int maxIndex = 0;
for (size_t i = 1; i < output.M(); i++) {
if (output(0, i) > output(0, maxIndex))
maxIndex = i;
}
return maxIndex;
}
int style(int H, int W, int R[500][500], int G[500][500], int B[500][500]) {
QNet::Net<double, Mat, QNet::sigmoid, QNet::dSigmoid, true, true> nn(N * N * 3, {10, 138, 4});
vector<vector<int>> r(H, vector<int>(W)), g(H, vector<int>(W)), b(H, vector<int>(W));
for (int i = 0; i < H; i++)
for (int j = 0; j < W; j++)
r[i][j] = R[i][j], g[i][j] = G[i][j], b[i][j] = B[i][j];
r = rsz(r), g = rsz(g), b = rsz(b);
Mat in(1, N * N * 3, 0);
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
in(0, i * N + j) = (float) r[i][j] / 255,
in(0, N * N + i * N + j) = (float) g[i][j] / 255,
in(0, N * N * 2 + i * N + j) = (float) b[i][j] / 255;
}
}
auto res = nn.forward(in);
return predict(res) + 1;
}
相关推荐
评论
共 34 条评论,欢迎与作者交流。
正在加载评论...