我用C编写了一个非常简单的人工智能,并通过Rails将其连接到web。它学习了fizzbuzz问题。我写这篇文章是因为20年后我又回到了就业市场。当然,那个时候github根本不存在。所以,现在找工作的环境完全不同了,我没有招聘人员正在寻找的资产。所以我在求你帮忙。我要求对此进行代码审查,以及你们给我的任何建议。https://github.com/rickcockerham/fizzbuzz-ai谢谢,瑞克
// Fbai class.
// A C program that learns the Fizz Buzz problem.
// I used my neural network code that I downloaded years ago and modified.
// I have simplified the neural network code to make it only has complicated as
// it needs to be to solve this simple problem. One hidden layer and four separate
// networks is all it takes to solve the FB problem in 5-10 learning runs.
//################################################################################
#include
#include
#include
//nn constants
double Error, eta = 0.1; // a very slow learning rate
double alpha = 0.90, smallwt = 0.2;
#define PASSES 1 //passes per training run.
#define NUMPATTERNS 100 // 1-100
#define NUMHIDDEN 1 // hidden layers
#define NUMINPUTS 1 //number of inputs. In this case it's just a single integer.
#define NNS 4 // one neural network output per norm,fizz,buzz,fizzbuzz
double expected[NNS][NUMPATTERNS+1]; //holds the test data.
// The weights and outputs of the neural network.
double WeightIH[NNS][NUMINPUTS+1][NUMHIDDEN+1];//2. bias and input
double WeightHO[NNS][NUMHIDDEN+1];
double SumH[NNS][NUMPATTERNS+1][NUMHIDDEN+1], Hidden[NNS][NUMPATTERNS+1][NUMHIDDEN+1];
double SumO[NNS][NUMPATTERNS+1], Output[NNS][NUMPATTERNS+1];
double DeltaO[NNS], SumDOW[NNS][NUMHIDDEN+1], DeltaH[NNS][NUMHIDDEN+1];
double DeltaWeightIH[NNS][NUMINPUTS+1][NUMHIDDEN+1], DeltaWeightHO[NNS][NUMHIDDEN+1];
// The names of each network.
#define NORM 0
#define FIZZ 1
#define BUZZ 2
#define FIZZBUZZ 3
void train();
int gotest(int);
void init_ai();
//########################################
//random double. I would normally use lib sodium for better random.
double randd() {
return rand() / (double)RAND_MAX;
}
//################################################################################
// Ruby - C interface code.
// These functions are exposed to Ruby.
VALUE rb_fbai;
VALUE gotrain(VALUE self) {
train();//run a single training epoch.
return self;
}
VALUE reinit(VALUE self) {
init_ai();// clear the weights and start over.
return self;
}
VALUE valueat(VALUE self, VALUE testnum) {
int testx = NUM2INT(testnum); //convert VALUE to int
int returnval = gotest(testx);// get the AI output for this int.
return INT2NUM(returnval);//convert it back to a VALUE
}
// define interface methods between Ruby and C
void Init_fbai() {
rb_fbai = rb_define_class("Fbai", rb_cObject);
rb_define_method(rb_fbai, "gotrain", gotrain, 0);
rb_define_method(rb_fbai, "valueat", valueat, 1);
rb_define_method(rb_fbai, "reinit", reinit, 0);
init_ai();
}
//################################################################################
// the patterns are the training data. It's an array of 3 values 0 if it's just x, 1 if it's %3, 2 if it's %5, and 3 if it's %3&%5
// these are stored in three values for three sets of training patterns.
void patterns() {
memset(expected, 0.0, NNS * NUMPATTERNS+1); // all zeros first
for(int x = 1; x <= 100; x++) {
if(x % 3 == 0 && x % 5 == 0) {
expected[FIZZBUZZ][x] = 1.0; // set expexted[3][x] = 1.0 the rest are zeros.
} else if(x % 5 == 0) {
expected[BUZZ][x] = 1.0;
} else if(x % 3 == 0) {
expected[FIZZ][x] = 1.0;
} else {
expected[NORM][x] = 1.0;
}
}
}
//################################################################################
// clear all the weights and randomize them to learn the problem or learn it again.
void init_ai() {
int nn,hid,i;
////////////////////////////////////////////////////////////////////////////////
// init nn
memset(Output, 0.0, NNS * (NUMPATTERNS+1)); // all zeros for outputs.
memset(Hidden, 0.0, NNS * (NUMPATTERNS+1) * (NUMHIDDEN+1));
for(nn = 0; nn < NNS; nn++) { // loop through all neural nets
for( hid = 0 ; hid < NUMHIDDEN ; hid++ ) { /* initialize WeightIH and DeltaWeightIH */
for( i = 0; i <= 1; i++) {
DeltaWeightIH[nn][i][hid] = 0.0 ;
WeightIH[nn][i][hid] = 2.0 * ( randd() - 0.5 ) * smallwt ;
}
}
/* initialize WeightHO and DeltaWeightHO */
for( hid = 0 ; hid < NUMHIDDEN ; hid++ ) {
DeltaWeightHO[nn][hid] = 0.0 ;
WeightHO[nn][hid] = 2.0 * ( randd() - 0.5 ) * smallwt ;
}
}
patterns();
}
//#############################################
// Returns -1 if the output is wrong otherwise it will return 0-3 as the output.
int gotest(int x) {
double max = 0.0;
int nn, maxx = 0;
int correct = 0;
for(nn = 0; nn < NNS; nn++) {
if(Output[nn][x] > max) {
max = Output[nn][x];
maxx = nn;
if(1.0 == expected[nn][x]) correct = 1;
}
}
return (1 == correct ? maxx : -1);
}
//################################################################################
//################################################################################
//################################################################################
// training run for the AI.
// This code was downloaded from the internet many years ago and modifed for my other AI project ai-stocktrading.com
// I've simplifed it for this small demo.
void train() {
int hid, nn, p, epoch;
for(nn = 0; nn < NNS; nn++) { // loop through both neural nets
for( epoch = 0 ; epoch < PASSES ; epoch++) {
Error = 0.0;
for( p = 1 ; p < NUMPATTERNS+1 ; p++ ) { /* repeat for all the training patterns */
for( hid = 1 ; hid <= NUMHIDDEN ; hid++ ) { /* compute hidden unit activations */
SumH[nn][p][hid] = WeightIH[nn][0][hid] ;// bias against input 0
SumH[nn][p][hid] += expected[nn][p] * WeightIH[nn][1][hid]; //double np is the input 1-100
Hidden[nn][p][hid] = 1.0/(1.0 + exp(-SumH[nn][p][hid])) ;
}
/* compute output unit activations and errors */
SumO[nn][p] = WeightHO[nn][0]; // bias
for( hid = 1 ; hid <= NUMHIDDEN ; hid++ ) {
SumO[nn][p] += Hidden[nn][p][hid] * WeightHO[nn][hid] ;
}
// Sigmoidal Outputs
Output[nn][p] = 1.0/(1.0 + exp(-SumO[nn][p]));
//Sigmoidal Outputs, SSE
DeltaO[nn] = (expected[nn][p] - Output[nn][p]) * Output[nn][p] * (1.0 - Output[nn][p]) ;
for( hid = 1 ; hid <= NUMHIDDEN ; hid++ ) { /* 'back-propagate' errors to hidden layer */
SumDOW[nn][hid] = WeightHO[nn][hid] * DeltaO[nn];
DeltaH[nn][hid] = SumDOW[nn][hid] * Hidden[nn][p][hid] * (1.0 - Hidden[nn][p][hid]) ;
}
for( hid = 1; hid <= NUMHIDDEN ; hid++ ) { /* update weights WeightIH */
DeltaWeightIH[nn][0][hid] = eta * DeltaH[nn][hid] + alpha * DeltaWeightIH[nn][0][hid] ;
WeightIH[nn][0][hid] += DeltaWeightIH[nn][0][hid] ;
DeltaWeightIH[nn][1][hid] = eta * expected[nn][p] * DeltaH[nn][hid] + alpha * DeltaWeightIH[nn][1][hid];
WeightIH[nn][1][hid] += DeltaWeightIH[nn][1][hid] ;
}
/* update weights WeightHO */
DeltaWeightHO[nn][0] = eta * DeltaO[nn] + alpha * DeltaWeightHO[nn][0] ;
WeightHO[nn][0] += DeltaWeightHO[nn][0] ;
for( hid = 1 ; hid <= NUMHIDDEN ; hid++ ) {
DeltaWeightHO[nn][hid] = eta * Hidden[nn][p][hid] * DeltaO[nn] + alpha * DeltaWeightHO[nn][hid] ;
WeightHO[nn][hid] += DeltaWeightHO[nn][hid] ;
}
}//for num patterns
} // for epochs
}//for nns
}// train index_controller.rb
require 'fbai'
class IndexController < ApplicationController
layout false
# I need the AI to survive between calls to the testit function.
$ai = Fbai.new
def index
#reset the AI
$ai.reinit();
end
# this will return a string of numbers and their value (x,fizz,buzz,fizzbuzz)
def testit
ret = '';
for x in 1..100
data = $ai.valueat(x).to_i
ret << "#{x}="
if(-1 == data)
ret << '-err-'
elsif(0 == data)
ret << "#{x}"
elsif(2 != data)
ret << 'FIZZ'
end
if(1 < data)
ret << 'BUZZ'
end
ret << " "
end
render html: ret, layout: false
end
# this tells the AI to do one training pass. Then the testit function is called again to read the results.
def trainmore
$ai.gotrain()
render plain: 'success'
end
endindex.html.erb
div {
width:80px;
height:30px;
float:left;
border:1px solid black;
margin:1px;
}
.clearleft {
width:0px;
clear: left;
border: none;
}
Neural Network leaning the FizzBuzz problem...
Training run #0
<% for x in 0..99
if x % 5 == 0 %>
<% end %>
<%=x%>
<% end %>
<%= javascript_tag do %>
const donere = /err/;
var run = 0;
callnext_number();
const timeit = setInterval(callnext_number, 2000);
async function callnext_number() {
var ret = '';
promi = new Promise(fillin => {jQuery.ajax({
url: "index/testit",
type: "GET",
success: function(data){
var vals = data.split(' ');
console.log(vals);
for(let x = 1; x <= 100; x++) {
let ans = vals[x-1].split('=');
$('#val'+x).html(ans[1]);
if(donere.exec(ans[1])) {
$('#val'+x).css("background-color","#ff3333");
} else {
$('#val'+x).css("background-color","white");
}
}
if(donere.exec(data)) {
jQuery.ajax({url: "index/trainmore",type: "GET"});
} else {
clearInterval(timeit);
alert('done');
}
$('#runnumber').html(++run);
},
error: function(data) {
alert(data);
}//return
});
}
);
await promi;
return;
}//call next
<% end %>发布于 2022-11-09 14:20:36
我不能评论代码中的ruby部分,因为我不知道ruby。
C中没有类,但您可以使用结构来实现相同的目的。
尝试将行长度保持在100个字符以下,现在所有IDE都支持超过80个字符,但默认情况下并非所有IDE和编辑器都提供行换。
尽量将函数长度保持在一个屏幕上,那些大于这些功能的函数很难理解和维护。
很难读、写、调试和维护使用全局变量的程序。程序中的任何函数都可以修改全局变量,因此需要在对代码进行更改之前对每个函数进行检查。在C和C++中,全局变量会影响命名空间,如果它们在多个文件中定义,它们可能导致链接错误。这个堆叠溢出问题的答案提供了更全面的解释。
还不清楚是否使用了全局变量Error,尽管它最终被分配到了void train()函数中。
如果Error和eta是真正的常量,那么现代C现在支持常量声明:
const double Error = 1;
const double eta = 0.1;在上世纪70年代和80年代C的原始版本中,变量必须声明在函数的顶部。现在情况不再是这样了,这是一种建议的编程实践,可以根据需要声明变量。在C语言中,该语言不提供变量的默认初始化,因此变量应该作为声明的一部分进行初始化。为了提高可读性和可维护性,每个变量都应该在自己的行上声明和初始化。
现代版本的C允许在循环中初始化循环变量:
void train() {
size_t hid;
for (size_t nn = 0; nn < NNS; nn++) { // loop through both neural nets
for (size_t epoch = 0; epoch < PASSES; epoch++) {
for (size_t p = 1; p < NUMPATTERNS + 1; p++) { /* repeat for all the training patterns */
for (hid = 1; hid <= NUMHIDDEN; hid++) { /* compute hidden unit activations */
SumH[nn][p][hid] = WeightIH[nn][0][hid];// bias against input 0
SumH[nn][p][hid] += expected[nn][p] * WeightIH[nn][1][hid]; //double np is the input 1-100
Hidden[nn][p][hid] = 1.0 / (1.0 + exp(-SumH[nn][p][hid]));
}对于数组索引,
size_t而不是int,对于索引数组,size_t类型比int更可取,它是一个无符号类型,因此它不会变成负值,它还将根据编译器和操作系统对最大可能的数组进行适当的大小调整( int或long)。在上面的本地声明示例中,size_t类型替代了int。
void train()函数太复杂,可以分解成较小的函数,一个明显的例子是以下代码:
SumO[nn][p] = WeightHO[nn][0]; // bias
for (hid = 1; hid <= NUMHIDDEN; hid++) {
SumO[nn][p] += Hidden[nn][p][hid] * WeightHO[nn][hid];
}这里也有一个叫做单一责任原则的编程原则。单一责任原则指出:
每个模块、类或函数都应该对软件提供的功能的单个部分负责,而该责任应该完全由该模块、类或函数封装。
对于块注释,我个人更喜欢原始的C风格评论:
/* Fbai class.
* A C program that learns the Fizz Buzz problem.
* I used my neural network code that I downloaded years ago and modified.
* I have simplified the neural network code to make it only has complicated as
* it needs to be to solve this simple problem. One hidden layer and four separate
* networks is all it takes to solve the FB problem in 5-10 learning runs.
*/对于其他评论,我更喜欢更新的//注释样式。您所使用的样式是一致的,不要像在void train()函数中那样混合代码块中的类型。
不清楚为什么有3行代码分隔符注释:
//################################################################################
//################################################################################
//################################################################################https://codereview.stackexchange.com/questions/281077
复制相似问题