def _raw_linear_transform(self, X, traits=None, user_input=None): """ Do linear_transform of X, and return both raw OPU output and decoded output in a tuple """ if traits is None: assert self._runner, "Call fit1d or fit2d before linear_transform" traits = self._runner.traits if user_input is None: user_input = OpuUserInput.from_traits(X, traits) if self._s.simulated: prepared_X = X else: assert self.device.acq_state.value != AcqState.online.value, \ "Can't do linear transform when acquisition is" \ " in online mode, only single vectors" assert self._runner.t.input_roi_strategy == InputRoiStrategy.full, \ "ROI strategy must be full for linear_transform to be correct.\n" \ "Set input_roi_strategy attribute to InputRoiStrategy.full." # X2 is now numpy 2D, whatever the initial shape and the type (torch or numpy) X2 = user_input.reshape_input(raveled_features=True, leave_single_dim=True) try: import lightonopu.linear_reconstruction as reconstruction except ImportError: raise RuntimeError( "Need a lightonopu version with linear_reconstruction module" ) start = time.time() prepared_X = reconstruction.encode_batch(X2) self._trace(f"Encoding time {time.time() - start} s") # Restore the dimension after batch encoding to something suitable for formatting prepared_X = user_input.unravel_features(prepared_X) # Run the OPU transform prepared_input = OpuUserInput.from_traits(prepared_X, traits) start = time.time() with self.device.acquiring(n_images=self._s.n_samples_by_pass): rp_opu = self._runner.transform(prepared_input, linear=True) self._trace(f"Transform time {time.time() - start} s") if self._s.simulated: result_ctx = rp_opu else: # Decoding forgets about the context, re-add it to result afterwards start = time.time() result = reconstruction.decode_batch(rp_opu) self._trace(f"Decoding time {time.time() - start} s") result_ctx = ContextArray(result, rp_opu.context) return rp_opu, result_ctx
def __fit(self, X, n_features: IntOrTuple, packed: bool, online: bool, is_2d_features: bool, **override): """Internal working of the fitXd calls Instantiates a TransformRunner, and start online acq if needs be. """ if X is not None: # Input is provided, do the fit with user input user_input = OpuUserInput.from_input(X, packed, is_2d_features, n_features) tr_settings = self._tr_settings(no_input=False, **override) self._runner = FitTransformRunner(self._s, tr_settings, user_input, device=self.device, disable_pbar=self.disable_pbar) else: # Only dimensions are provided, no fitting happens on input assert n_features, "either input vector or n_features must be specified" # tr_settings has no input_roi, since it uses X to compute it tr_settings = self._tr_settings(no_input=True, **override) traits = InputTraits(n_features, packed) self._runner = TransformRunner(self._s, tr_settings, traits, device=self.device, disable_pbar=self.disable_pbar) self._acq_stack.close() if online: if self._s.no_single_transform: raise RuntimeError( "Online transform isn't available with this OPU") # Start acquisition only if online. Batch transform start their own. self._acq_stack.enter_context(self.device.acquiring(online=True))
def transform(self, input_vectors: OpuUserInput, linear=False): """Do the OPU transform of input If batch transform, device acquisition must be started """ assert self.device.active and input_vectors.traits == self._traits if not self.s.simulated: input_vectors.binary_check() else: # In simulated mode, SimulatedDevice must whether the transform is linear or not self.device._linear = linear context = self._get_context() self._pre_print(self.device, input_vectors.n_samples) X = input_vectors.reshape_input() if input_vectors.is_batch: t0 = time.time() nb_retries = 0 # Batch transform, allocate the result, progress bar, and start acquisition # allocation of empty vector for iteration n_samples = input_vectors.n_samples_s Y = np.empty((n_samples, self._out_size), dtype=self.device.output_dtype) self._trace("Y allocated") indices = self.indices(input_vectors.n_samples_s) with Progress(n_samples, "OPU: transform", self.disable_pbar) as p: # iterate over consecutive pairs of indices # (https://stackoverflow.com/a/21303286) for i, (start, end) in enumerate(zip(indices, indices[1:])): c = self.__batch_transform(X[start:end], Y[start:end], i) p.update(end - start) nb_retries += c t1 = time.time() # Fill context from opu settings at the end of the transform context.from_opu(self.device, dt.datetime.fromtimestamp(t0), dt.datetime.fromtimestamp(t1)) else: Y, nb_retries = self.__single_transform(X) context.from_opu(self.device, dt.datetime.now()) if nb_retries: self._print("OPU number of retries: ", nb_retries) return ContextArray(Y, context)
def linear_transform(self, X, encoder_cls=NoEncoding, decoder_cls=NoDecoding) -> TransformOutput: """ Do a linear transform of X, for Nitro (non-linear) photonic cores. Parameters ---------- X: np.ndarray or torch.Tensor input vector, or batch of input vectors. Each vector must have the same dimensions as the one given in `fit1d` or `fit2d`. encoder_cls: encoding.base.BaseTransformer, optional class or instance of class that transform the input into binary vectors to be processed by the opu. decoder_cls: encoding.base.BaseTransformer, optional class or instance of class that transforms the output of the opu back into the appropriate format. Returns ------- Y: np.ndarray or torch.Tensor complete array of nonlinear random projections of X, of size self.n_components If input is an ndarray, type is actually ContextArray, with a context attribute to add metadata """ assert self._runner, "Call fit1d or fit2d before linear_transform" traits = self._runner.traits if traits.packed: # TODO implement for packed raise RuntimeError( "Linear transform isn't yet implemented for packed input :/") if inspect.isclass(encoder_cls): encoder = encoder_cls() else: encoder = encoder_cls X_enc = encoder.transform(X) user_input = OpuUserInput.from_traits(X_enc, traits) _, result_ctx = self._raw_linear_transform(X_enc, traits, user_input) # Decoding, add context, and optional convert back to torch if needed output = self._post_transform(result_ctx, user_input, encoder, decoder_cls) # Rescale the output, intentionally after the decoding step if self.rescale is OutputRescaling.variance: n_features = user_input.n_features_s output = output / (self._s.stdev * sqrt(n_features)) elif self.rescale is OutputRescaling.norm: output = output / (self._s.stdev * sqrt(self.n_components)) return output
def transform(self, X, encoder_cls=NoEncoding, decoder_cls=NoDecoding) -> TransformOutput: """ Performs the nonlinear random projections of one or several input vectors. The `fit1d` or `fit2d` method must be called before, for setting vector dimensions or online option. If you need to transform one vector after each other, add `online=True` in the fit function. Parameters ---------- X: np.ndarray or torch.Tensor input vector, or batch of input vectors. Each vector must have the same dimensions as the one given in `fit1d` or `fit2d`. encoder_cls: encoder.base.BaseTransformer, optional class or instance of class that transform the input into binary vectors to be processed by the opu. decoder_cls: encoder.base.BaseTransformer, optional class or instance of class that transforms the output of the opu back into the appropriate format. Returns ------- Y: np.ndarray or torch.Tensor complete array of nonlinear random projections of X, of size self.n_components If input is an ndarray, type is actually ContextArray, with a context attribute to add metadata """ assert self._runner, "Call fit1d or fit2d before transform" assert self.device.active, "OPU device isn't active, use opu.open() or \"with opu:\"" if inspect.isclass(encoder_cls): encoder = encoder_cls() else: encoder = encoder_cls X_enc = encoder.transform(X) user_input = OpuUserInput.from_traits(X_enc, self._runner.traits) self._debug(str(user_input)) if user_input.is_batch and not self._s.simulated: # With batch input start acquisition first assert self.device.acq_state.value != AcqState.online.value, \ "Can't transform a batch of vectors when acquisition is" \ " in online mode, only single vectors" with self.device.acquiring(n_images=self._s.n_samples_by_pass): out = self._runner.transform(user_input) else: out = self._runner.transform(user_input) return self._post_transform(out, user_input, encoder, decoder_cls)
def roi_compute(self, user_input: OpuUserInput): # with automatic input roi, compute number of ones before # This case is met only when input is provided to runner creation # (i.e. opu.fit with an input) # # Count number of ones, and then compute ROI with "auto" strategy roi = InputRoi(self.s.input_shape, self.s.ones_range) n_ones = self._count_ones(user_input.reshape_input(), user_input.traits.packed) n_features_s = self._traits.n_features_s self._debug("Counted an average of" " {:.2f} ones in input of size {} ({:.2f}%)." .format(n_ones, n_features_s, 100 * n_ones / n_features_s)) return roi.compute_roi(self.t.input_roi_strategy, self._traits.n_features, n_ones, self.ones_info)
def transform(self, X) -> TransformOutput: """ Performs the nonlinear random projections of one or several input vectors. The `fit1d` or `fit2d` method must be called before, for setting vector dimensions or online option. If you need to transform one vector after each other, Parameters ---------- X: np.ndarray or torch.Tensor input vector, or batch of input vectors. Each vector must have the same dimensions as the one given in `fit1d` or `fit2d`. Returns ------- Y: np.ndarray or torch.Tensor complete array of nonlinear random projections of X, of size self.n_components If input is an ndarray, type is actually ContextArray, with a context attribute to add metadata """ assert self._runner, "Call fit1d or fit2d before transform" assert self.device.active, "OPU device isn't active, use opu.open() or \"with opu:\"" user_input = OpuUserInput.from_traits(X, self._runner.traits) self._debug(str(user_input)) if user_input.is_batch: # With batch input start acquisition first assert self.device.acq_state != AcqState.online, \ "Can't transform a batch of vectors when acquisition is" \ " in online mode, only single vectors" with self.device.acquiring(n_images=self._s.n_samples_by_pass): out = self._runner.transform(user_input) else: out = self._runner.transform(user_input) Y = user_input.reshape_output(out) # if the input is a tensor, return a tensor in CPU memory if user_input.is_tensor: # noinspection PyPackageRequirements import torch return torch.from_numpy(Y) else: return Y