/ utils.cpp
utils.cpp
1 #include <sys/stat.h> // mkdir 2 3 #include "nn.h" 4 5 #define MAX_NODES 30 6 #define UTILS_DEBUG false 7 8 9 // void log_print(FILE* file, const char* format, ...) { 10 // va_list args; 11 12 // // Write to stdout 13 // va_start(args, format); 14 // vprintf(format, args); 15 // va_end(args); 16 17 // // Write to the file 18 // va_start(args, format); 19 // vfprintf(file, format, args); 20 // va_end(args); 21 // } 22 23 void log_print(const char* format, ...) { 24 25 va_list args; 26 27 // Write to stdout 28 va_start(args, format); 29 vprintf(format, args); 30 va_end(args); 31 32 // Write to the file 33 FILE* file = fopen("./generated/checkpoints/stdout.txt", "a"); 34 if (!file) { 35 printf("Error opening file\n"); 36 exit(1); 37 } 38 39 va_start(args, format); 40 vfprintf(file, format, args); 41 va_end(args); 42 43 fclose(file); 44 } 45 46 47 void log_print_macros(void){ 48 // size of the training job 49 log_print("N_SAMPLES: %i\n", N_SAMPLES); 50 log_print("BATCH_SIZE: %i\n", BATCH_SIZE); 51 log_print("NUM_EP: %i\n", NUM_EP); 52 53 // optimization related 54 log_print("IS_LOAD: %i\n", IS_LOAD); 55 log_print("IS_STOCHASTIC: %i\n", IS_STOCHASTIC); 56 log_print("LR: %f\n", LR); 57 58 // misc 59 log_print("SAVE_EVERY: %i\n", SAVE_EVERY); 60 log_print("DEVICE: %i\n", DEVICE); 61 } 62 63 void flush_io_buffers(void){ 64 mkdir("./generated", 0755); 65 mkdir("./generated/checkpoints", 0755); 66 fclose(fopen("./generated/log.txt", "w")); 67 fclose(fopen("./generated/checkpoints/train_loss.txt", "w")); 68 fclose(fopen("./generated/checkpoints/val_loss.txt", "w")); 69 fclose(fopen("./generated/checkpoints/stdout.txt", "w")); 70 } 71 72 void save_loss(const char* f_name, float loss){ 73 // save them into 2 different files -- the resolution for the train loss is ofc higher, so doesn't make sense to write them into the same array (bc this would require skipping saving most of the training losses) 74 75 char path[40]; 76 snprintf(path, sizeof(char) * 40, "./generated/checkpoints/%s.txt", f_name); 77 78 FILE *f = fopen(static_cast<const char*>(path), "a"); 79 if (!f) { 80 printf("Error opening file\n"); 81 exit(1); 82 } 83 fprintf(f, "%.3f, ", loss); 84 fclose(f); 85 } 86 87 tuple* get_tuple(float val1, float val2){ 88 // todo: a better way of doing this? 89 tuple* out = (tuple*)checkMallocErrors(malloc(sizeof(tuple))); 90 out->item_1 = val1; 91 out->item_2 = val2; 92 return out; 93 } 94 95 // returns a null terminated string containing shape of the tensor 96 char* str_shape(tensor* t){ 97 char* buffer = (char*)checkMallocErrors(malloc(sizeof(char) * 13)); 98 99 if (t->num_dims==2){ 100 sprintf(buffer, "(%i, %i)\0", t->shape[0], t->shape[1]); 101 } else if (t->num_dims==3){ 102 sprintf(buffer, "(%i, %i, %i)\0", t->shape[0], t->shape[1], t->shape[2]); 103 } else if (t->num_dims==4){ 104 sprintf(buffer, "(%i, %i, %i, %i)\0", t->shape[0], t->shape[1], t->shape[2], t->shape[3]); 105 } else{ 106 printf("[str_shape] unexpected shape\n"); 107 exit(1); 108 } 109 return buffer; 110 } 111 112 113 void* checkMallocErrors(void* ptr) { 114 if (ptr == NULL){ 115 printf("[malloc] error: null pointer\n"); 116 exit(1); 117 } 118 return ptr; 119 } 120 121 void maybe_init_grad(tensor* t){ 122 if (!t->grad){ 123 t->grad = TensorLikeFill(t, 0.0); 124 } else { 125 if (UTILS_DEBUG) printf("[maybe_init_grad] %s->grad exists!\n", t->name); 126 } 127 } 128 129 char* random_chars(int num){ 130 // no need to increment for the null terminator, bc the for loop below is not inclusive of the last char; 131 // e.g.: num=3; the loop below will iterate 0-2; s[3] = '\0' 132 // not necessary to do sizeof(char) bc guarantied to be 1 133 char* s = (char*)checkMallocErrors(malloc(sizeof(char) * num)); 134 135 char offset = 'a'; 136 for (int i=0; i<num; i++){ 137 // my first thought was to use modulus, but it's wrong https://stackoverflow.com/a/6852396 138 // todo: still see non-printable chars in the tensor names 139 char sampled = rand() % 26; // 'z' - 'a' // 26 letters 140 s[i] = offset + sampled; 141 } 142 s[num] = '\0'; 143 return s; 144 } 145 146 void set_name(tensor* t, const char* name){ 147 // free the automatically set random name 148 // added by the constructor 149 // todo: "if (t->name){...}" for some reason leads to a double free error, although I'd expect the two syntaxes "if (t->name==NULL){...}" be the same in my case 150 // if (t->name==NULL){ 151 // free(t->name); 152 // } 153 154 // todo-low: small inefficiency of always allocating MAX_TENSOR_NAME 155 // even if user provided str is shorter 156 t->name = (char*)checkMallocErrors(malloc(sizeof(char) * MAX_TENSOR_NAME)); 157 158 int i=0; 159 bool is_break = false; 160 for (; !is_break && i<MAX_TENSOR_NAME-1; i++) { 161 t->name[i] = name[i]; 162 if (name[i] == '\0') 163 is_break = true; 164 } 165 166 if (!is_break && name[i+1] != '\0') { 167 printf("[set_name] Warning, specified name larger than MAX_TENSOR_NAME -- truncating\n"); 168 t->name[i+1] = '\0'; 169 } 170 } 171 172 173 // todo-low: keras like vis https://graphviz.org/Gallery/directed/neural-network.html 174 void graphviz(tensor* tens){ 175 FILE *f = fopen("./generated/graph.txt", "w"); 176 if (f == NULL) { 177 printf("[graphviz] Error opening file\n"); 178 exit(1); 179 } 180 181 fprintf(f, "digraph {\n"); 182 fprintf(f, "node [ordering=\"in\", fixedsize=shape shape=circle style=filled]\n"); 183 184 // will record pointers to all seen names -- to avid visiting same nodes twice, when 185 // exp 186 // / \ 187 // x1 x2 188 char* all_visited[MAX_NODES]; // (float*)checkMallocErrors(malloc(sizeof(float*) * MAX_NODES)); 189 // is used to index into all_visited 190 int idx_visited = 0; 191 192 std::deque <tensor*> ready; 193 ready.push_front(tens); 194 195 while (ready.size() > 0) { 196 tensor* t = ready.back(); ready.pop_back(); 197 198 const char* op_name = OP_NAMES[t->op_type]; 199 const char* op_color = VIS_COLORS[t->op_type]; 200 201 // op -> output (there's only 1 output) 202 fprintf(f, "%s_%s -> %s\n", op_name, t->name, t->name); 203 204 // for ops, hide unique postfixes -- vis regular name (one of NUM_OPS) instead of unique name 205 fprintf(f, "%s_%s [label=%s, fillcolor=%s, width=1.2, shape=diamond, style=\"rounded, filled\"]\n", op_name, t->name, op_name, op_color); 206 207 for (int i=0; i<t->num_inputs; i++){ 208 tensor* inp = t->inputs[i]; 209 210 // check if we already visited this node 211 bool is_visited = false; 212 for (int i=0; i<idx_visited; i++){ 213 // question-now: use of == on char arrays -- it should be CPP's overloaded? 214 if (all_visited[i] == inp->name) { 215 is_visited = true; 216 break; 217 } 218 } 219 220 // an input -> op 221 fprintf(f, "%s -> %s_%s\n", inp->name, op_name, t->name); 222 223 // for tensors, vis shapes instead of names 224 // label=\"{%s\\nshape=(%i, %i)}\"]\n", inp->name 225 fprintf(f, "%s [shape=record, label=\"{%s\\nshape=%s}\"]\n", inp->name, inp->name, str_shape(inp)); 226 227 // leafs don't have inputs to iterate over in the next iteration 228 if (!inp->is_leaf && !is_visited) { 229 ready.push_front(inp); 230 all_visited[idx_visited++] = inp->name; 231 } 232 233 } 234 } 235 fprintf(f, "}\n"); 236 fclose(f); 237 }