code: func (t *Trainer) handleCosts() (epochChan chan struct{}) {
  	nncost := t.nn.costProgress()
  
  	if t.PassDirect {
  		go func() {
  			for cost := range nncost {
  				switch c := cost.Data().(type) {
  				case float32:
  					t.cost <- float64(c)
  				case float64:
  					t.cost <- c
  				default:
  					// this should NEVER happen
  					panic(fmt.Sprintf("Unhandled cost type %T", c))
  				}
  			}
  		}()
  	} else {
  		epochChan = make(chan struct{})
  
  		// it collects the costs until the epoch chan signals that an epoch is done. Then the cost is averaged and sent down the d.cost channel
  		go func(epochChan chan struct{}) {
  			var collected []float64
  			for {
  				select {
  				case cost := <-nncost:
  					switch c := cost.Data().(type) {
  					case float32:
  						collected = append(collected, float64(c))
  					case float64:
  						collected = append(collected, c)
  					default:
  						// this should NEVER happen
  						panic(fmt.Sprintf("Unhandled cost type %T", c))
  					}
  				case <-epochChan:
  					var avg float64
  					for _, cost := range collected {
  						avg += cost
  					}
  
  					if len(collected) > 0 {
  						avg /= float64(len(collected))
  					}
  
  					t.cost <- avg
  					collected = collected[:0]
  				}
  			}
  		}(epochChan)
  	}
  	return
  }
docs: // handleCosts handles the costs from the neural network in two ways:
  //		1. pass: directly passes on the costs (which may come from multiple batches in an epoch)
  //		2. mean: calculates the mean of the costs and passes it on into d.cost
  //
  // If d.cost is nil, it simply returns. This method should be called after a check that d.cost is not nil