/ 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  }