def __init__(self, args): super().__init__() # Input layer layers = [ ConvLayer(args.cnn_in_channels, args.cnn_hidden_channels, num_classes=args.num_classes) ] # Hidden layers for _ in range(args.cnn_hidden_layers): layers.append( ConvLayer(args.cnn_hidden_channels, args.cnn_hidden_channels, num_classes=args.num_classes)) # Output layer layers.append( ConvLayer(args.cnn_hidden_channels, args.cnn_in_channels, num_classes=args.num_classes, normalize=False, layer_activation=None)) # Output layer self.model = nn.Sequential(*layers) self.residual = args.residual
def set_from_config(config_json, init=None, return_class=True): _id = config_json['_id'] '''number_of_block=config_json['number_of_block'] number_of_base_channel=config_json['number_of_base_channel'] depth=config_json['depth']''' layers = [] import numpy as np #''' conv_i=0 n_layers=2 k=48 input_filter_num=k*2 layers.append(ConvLayer("conv_adjust",k*2)) for i in range(3): for j in range(n_layers): layers.append(DenseNetLayer("DenseNetLayer_"+str(i*n_layers+j),k,4,3)) #input_filter_num=input_filter_num+k if i<2: input_filter_num=(input_filter_num+k*n_layers) #layers.append(PoolLayer_inner("PoolLayer_"+str(i),input_filter_num,'avg')) layers.append(ConvLayer("conv_"+str(i),input_filter_num,1,1)) layers.append(PoolLayer("pool_"+str(i),'avg',2)) #append(ConvLayer_blocks("ConvLayer_blocks_"+str(i),12,(i+1)*11+1,2,2)) # layers.append(PoolLayer("pool_"+str(i),'avg',8,use_bn=True, activation='relu')) layers.append(FCLayer("fc_0",10)) #_id, _type,number_of_block=4, kernel_size=2 ''' for _i, layer_config in enumerate(config_json['layers']): layer_init = init['layers'][_i] if init is not None else None #print(layer_config['name']) layer = get_layer_by_name(layer_config['name']) layers.append(layer.set_from_config(layer_config, layer_init)) #''' if return_class: return LayerCascade(_id, layers) else: return _id, layers
def set_standard_convnet(self, data_provider, conv_blocks_config, fc_block_config, weight_decay, drop_scheme, bn_epsilon, bn_decay, print_info=True, **kwargs): assert (isinstance(data_provider, DataProvider)) self.net_config = { 'weight_decay': weight_decay, 'bn_epsilon': bn_epsilon, 'bn_decay': bn_decay, 'drop_scheme': drop_scheme, } image_size = data_provider.data_shape[0] layers = [] conv_id = 0 for _i, block_config in enumerate(conv_blocks_config): num_layers, kernel_size, filter_num = block_config for _j in range(num_layers): keep_prob = 1.0 if 'conv' in drop_scheme['type']: keep_prob = 1.0 if _i + _j == 0 else drop_scheme.get( 'conv_drop', 1.0) conv_layer = ConvLayer('conv_%d' % conv_id, filter_num, kernel_size=kernel_size, keep_prob=keep_prob, pre_activation=False) conv_id += 1 layers.append(conv_layer) if _i < len(conv_blocks_config) - 1: keep_prob = 1.0 if 'pool' in drop_scheme['type']: keep_prob = drop_scheme.get('pool_drop', 1.0) pool_layer = PoolLayer('pool_%d' % _i, 'max', keep_prob=keep_prob, pre_activation=False) layers.append(pool_layer) image_size = image_size // 2 global_avg_pool = PoolLayer('pool_%d' % len(conv_blocks_config), 'avg', kernel_size=image_size, strides=image_size, pre_activation=False) layers.append(global_avg_pool) for _i, units in enumerate(fc_block_config): keep_prob = 1.0 if 'fc' in drop_scheme['type']: keep_prob = drop_scheme.get('fc_drop', 1.0) fc_layer = FCLayer('fc_%d' % _i, units, keep_prob=keep_prob) layers.append(fc_layer) final_fc_layer = FCLayer('fc_%d' % len(fc_block_config), data_provider.n_classes, use_bn=False, use_bias=True, activation=None) layers.append(final_fc_layer) self.layer_cascade = LayerCascade('SimpleConvNet', layers) if print_info: pass return self
def set_standard_net(data_shape, n_classes, start_planes, alpha, block_per_group, total_groups, downsample_type, bottleneck=4, ops_order='bn_act_weight', dropout_rate=0, final_bn=True, no_first_relu=True, use_depth_sep_conv=False, groups_3x3=1, path_drop_rate=0, use_zero_drop=True, drop_only_add=False): image_channel, image_size = data_shape[0:2] addrate = alpha / (block_per_group * total_groups) # add pyramid_net # initial conv features_dim = start_planes if ops_order == 'weight_bn_act': init_conv_layer = ConvLayer(image_channel, features_dim, kernel_size=3, use_bn=True, act_func='relu', dropout_rate=0, ops_order=ops_order) elif ops_order == 'act_weight_bn': init_conv_layer = ConvLayer(image_channel, features_dim, kernel_size=3, use_bn=True, act_func=None, dropout_rate=0, ops_order=ops_order) elif ops_order == 'bn_act_weight': init_conv_layer = ConvLayer(image_channel, features_dim, kernel_size=3, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) else: raise NotImplementedError if final_bn: init_bn_layer = IdentityLayer(features_dim, features_dim, use_bn=True, act_func=None, dropout_rate=0, ops_order=ops_order) transition2blocks = TransitionBlock( [init_conv_layer, init_bn_layer]) else: transition2blocks = TransitionBlock([init_conv_layer]) blocks = [transition2blocks] planes = start_planes for group_idx in range(total_groups): for block_idx in range(block_per_group): if group_idx > 0 and block_idx == 0: stride = 2 image_size //= 2 else: stride = 1 # prepare the residual block planes += addrate if stride == 1: shortcut = IdentityLayer(features_dim, features_dim, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) else: if downsample_type == 'avg_pool': shortcut = PoolingLayer(features_dim, features_dim, 'avg', kernel_size=2, stride=2, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) elif downsample_type == 'max_pool': shortcut = PoolingLayer(features_dim, features_dim, 'max', kernel_size=2, stride=2, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) else: raise NotImplementedError out_plane = int(round(planes)) if out_plane % groups_3x3 != 0: out_plane -= out_plane % groups_3x3 # may change to += if no_first_relu: in_bottle = ConvLayer(features_dim, out_plane, kernel_size=1, use_bn=True, act_func=None, dropout_rate=dropout_rate, ops_order=ops_order) else: in_bottle = ConvLayer(features_dim, out_plane, kernel_size=1, use_bn=True, act_func='relu', dropout_rate=dropout_rate, ops_order=ops_order) if use_depth_sep_conv: cell_edge = DepthConvLayer(out_plane, out_plane, kernel_size=3, stride=stride, use_bn=True, act_func='relu', dropout_rate=dropout_rate, ops_order=ops_order) else: cell_edge = ConvLayer(out_plane, out_plane, kernel_size=3, stride=stride, groups=groups_3x3, use_bn=True, act_func='relu', dropout_rate=dropout_rate, ops_order=ops_order) cell = TreeNode(child_nodes=[None], edges=[cell_edge], in_channels=out_plane, out_channels=out_plane, split_type=None, merge_type=None, path_drop_rate=path_drop_rate, use_zero_drop=use_zero_drop, drop_only_add=drop_only_add) out_bottle = ConvLayer(out_plane, out_plane * bottleneck, kernel_size=1, use_bn=True, act_func='relu', dropout_rate=dropout_rate, ops_order=ops_order) residual_block = ResidualBlock(cell, in_bottle, out_bottle, shortcut, final_bn=final_bn) blocks.append(residual_block) features_dim = out_plane * bottleneck if ops_order == 'weight_bn_act': global_avg_pool = PoolingLayer(features_dim, features_dim, 'avg', kernel_size=image_size, stride=image_size, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) elif ops_order == 'act_weight_bn': global_avg_pool = PoolingLayer(features_dim, features_dim, 'avg', kernel_size=image_size, stride=image_size, use_bn=False, act_func='relu', dropout_rate=0, ops_order=ops_order) elif ops_order == 'bn_act_weight': global_avg_pool = PoolingLayer(features_dim, features_dim, 'avg', kernel_size=image_size, stride=image_size, use_bn=True, act_func='relu', dropout_rate=0, ops_order=ops_order) else: raise NotImplementedError transition2classes = TransitionBlock([global_avg_pool]) blocks.append(transition2classes) classifier = LinearLayer(features_dim, n_classes, bias=True) tree_node_config = { 'use_avg': True, 'bn_before_add': False, 'path_drop_rate': path_drop_rate, 'use_zero_drop': use_zero_drop, 'drop_only_add': drop_only_add, } return PyramidNet(blocks, classifier, ops_order, tree_node_config, groups_3x3)
def deepen(self, idx, new_layer_config, input_dim): assert idx < len(self.layers), 'Index out of range: %d' % idx if new_layer_config['name'] == 'fc': assert idx == len(self.layers) - 1 or isinstance( self.layers[idx + 1], FCLayer), 'Invalid' assert isinstance(self.layers[idx], FCLayer) or isinstance( self.layers[idx], PoolLayer), 'Invalid' # prepare the new fc layer units = input_dim for _i in range(idx, -1, -1): if isinstance(self.layers[_i], FCLayer): units = self.layers[_i].units break elif isinstance(self.layers[_i], ConvLayer): units = self.layers[_i].filter_num break fc_idx = 0 for _i in range(0, idx + 1): if isinstance(self.layers[_i], FCLayer): fc_idx += 1 _id = 'fc_%d' % fc_idx # change the id of following fc layers for _i in range(idx + 1, len(self.layers)): if isinstance(self.layers[_i], FCLayer): self.layers[_i].id = 'fc_%d' % (fc_idx + 1) fc_idx += 1 prev_layer = None for _i in range(idx, -1, -1): if self.layers[_i].ready: prev_layer = self.layers[_i] break assert prev_layer is not None, 'Invalid' new_fc_layer = FCLayer(_id, units, ready=False, **new_layer_config) # insert the new layer into the cascade self.layers = self.layers[:idx + 1] + [new_fc_layer ] + self.layers[idx + 1:] return new_fc_layer, prev_layer elif new_layer_config['name'] == 'conv': assert idx == len(self.layers) - 1 or not isinstance( self.layers[idx + 1], FCLayer), 'Invalid' assert isinstance(self.layers[idx], ConvLayer) or isinstance( self.layers[idx], FCLayer), 'Invalid' # prepare the new conv layer filter_num = input_dim for _i in range(idx, -1, -1): if isinstance(self.layers[_i], ConvLayer): filter_num = self.layers[_i].filter_num break conv_idx = 0 for _i in range(0, idx + 1): if isinstance(self.layers[_i], ConvLayer): conv_idx += 1 _id = 'conv_%d' % conv_idx # change the id of following conv layers for _i in range(idx + 1, len(self.layers)): if isinstance(self.layers[_i], ConvLayer): self.layers[_i].id = 'conv_%d' % (conv_idx + 1) conv_idx += 1 prev_layer = None for _i in range(idx, -1, -1): if self.layers[_i].ready: prev_layer = self.layers[_i] break assert prev_layer is not None, 'Invalid' new_conv_layer = ConvLayer(_id, filter_num, ready=False, **new_layer_config) self.layers = self.layers[:idx + 1] + [new_conv_layer ] + self.layers[idx + 1:] return new_conv_layer, prev_layer else: raise ValueError('Not support to insert a %s layer' % new_layer_config['name'])
def set_standard_dense_net(self, data_provider: DataProvider, growth_rate, depth, total_blocks, keep_prob, weight_decay, model_type, first_ratio=2, reduction=1.0, bc_ratio=4, bn_epsilon=1e-5, bn_decay=0.9, print_info=True, pre_activation=True, **kwargs): self.net_config = { 'model_type': model_type, 'weight_decay': weight_decay, 'first_ratio': first_ratio, 'reduction': reduction, 'bc_ratio': bc_ratio, 'bn_epsilon': bn_epsilon, 'bn_decay': bn_decay, 'pre_activation': pre_activation, } image_size = data_provider.data_shape[0] first_output_features = growth_rate * first_ratio bc_mode = (model_type == 'DenseNet-BC') layers_per_block = (depth - (total_blocks + 1)) // total_blocks if bc_mode: layers_per_block = layers_per_block // 2 # initial conv if pre_activation: init_conv_layer = ConvLayer('conv_0', first_output_features, kernel_size=3, activation=None, use_bn=False) else: init_conv_layer = ConvLayer('conv_0', first_output_features, kernel_size=3, pre_activation=False) init_transition = TransitionBlock('T_0_first', [init_conv_layer]) self.blocks = [init_transition] # Dense Blocks in_features_dim = first_output_features for block_idx in range(1, total_blocks + 1): miniblocks = [] block_id = 'D_%d' % block_idx for miniblock_idx in range(1, layers_per_block + 1): miniblock_id = 'M_%d' % miniblock_idx in_bottle = None if bc_mode: bottelneck_layer = ConvLayer('conv_0', growth_rate * bc_ratio, kernel_size=1, keep_prob=keep_prob, pre_activation=pre_activation) in_bottle = LayerCascade('in_bottle', [bottelneck_layer]) branch_0 = LayerCascade('B_0', [ ConvLayer('conv_0', growth_rate, kernel_size=3, keep_prob=keep_prob, pre_activation=pre_activation) ]) miniblocks.append(LayerMultiBranch(miniblock_id, [branch_0], in_bottle=in_bottle)) dense_block = DenseBlock(block_id, miniblocks) self.blocks += [dense_block] out_features_dim = dense_block.out_features_dim(in_features_dim) if block_idx != total_blocks: out_features_dim = int(out_features_dim * reduction) transition_id = 'T_%d_middle' % block_idx conv_layer = ConvLayer('conv_0', out_features_dim, kernel_size=1, keep_prob=keep_prob, pre_activation=pre_activation) avg_pool_layer = PoolLayer('pool_0', 'avg', kernel_size=2, strides=2) transition = TransitionBlock(transition_id, [conv_layer, avg_pool_layer]) self.blocks.append(transition) image_size = image_size // 2 in_features_dim = out_features_dim # Transition to classes if pre_activation: global_avg_pool = PoolLayer('pool_0', 'avg', kernel_size=image_size, strides=image_size, activation='relu', use_bn=True) else: global_avg_pool = PoolLayer('pool_0', 'avg', kernel_size=image_size, strides=image_size, pre_activation=False) final_fc_layer = FCLayer('fc_0', data_provider.n_classes, use_bn=False, use_bias=True, activation=None) transition_to_classes = TransitionBlock('T_to_classes', [global_avg_pool, final_fc_layer]) self.blocks.append(transition_to_classes) # print information about the network if print_info: print('Set Standard %s' % model_type) if not bc_mode: print('Build %s model with %d blocks, ' '%d composite layers each.' % (model_type, total_blocks, layers_per_block)) if bc_mode: print('Build %s model with %d blocks, ' '%d bottleneck layers and %d composite layers each.' % ( model_type, total_blocks, layers_per_block, layers_per_block)) print('Reduction at transition layers: %.2f' % reduction) return self
def set_standard_net(data_shape, n_classes, start_planes, block_per_group, total_groups, downsample_type='avg_pool', ops_order='bn_act_weight', dropout_rate=0, use_identity=False, use_depth_sep_conv=False, groups_3x3=1, path_drop_rate=0, use_zero_drop=True, drop_only_add=False): image_channel, image_size = data_shape[0:2] # initial conv features_dim = start_planes if ops_order == 'weight_bn_act': init_conv_layer = ConvLayer(image_channel, features_dim, kernel_size=3, use_bn=True, act_func='relu', dropout_rate=0, ops_order=ops_order) elif ops_order == 'act_weight_bn': init_conv_layer = ConvLayer(image_channel, features_dim, kernel_size=3, use_bn=True, act_func=None, dropout_rate=0, ops_order=ops_order) elif ops_order == 'bn_act_weight': init_conv_layer = ConvLayer(image_channel, features_dim, kernel_size=3, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) else: raise NotImplementedError transition2blocks = TransitionBlock([init_conv_layer]) blocks = [transition2blocks] if use_identity: assert 'pool' in downsample_type, 'Error' for group_idx in range(total_groups): for block_idx in range(block_per_group): if downsample_type is None and group_idx > 0 and block_idx == 0: stride = 2 image_size //= 2 out_dim = features_dim * 2 else: stride = 1 out_dim = features_dim # prepare the chain block if use_identity: cell_edge = IdentityLayer(features_dim, features_dim, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) elif use_depth_sep_conv: cell_edge = DepthConvLayer(features_dim, out_dim, kernel_size=3, stride=stride, use_bn=True, act_func='relu', dropout_rate=dropout_rate, ops_order=ops_order) else: cell_edge = ConvLayer(features_dim, out_dim, kernel_size=3, stride=stride, groups=groups_3x3, use_bn=True, act_func='relu', dropout_rate=dropout_rate, ops_order=ops_order) cell = TreeNode(child_nodes=[None], edges=[cell_edge], in_channels=features_dim, out_channels=out_dim, split_type=None, merge_type=None, path_drop_rate=path_drop_rate, use_zero_drop=use_zero_drop, drop_only_add=drop_only_add) chain_block = ChainBlock(cell) blocks.append(chain_block) features_dim = out_dim if group_idx != total_groups - 1 and downsample_type is not None: if 'pool' in downsample_type: conv_layer = ConvLayer(features_dim, 2 * features_dim, kernel_size=1, stride=1, use_bn=True, act_func='relu', dropout_rate=dropout_rate, ops_order=ops_order) features_dim *= 2 if downsample_type == 'avg_pool': pool_layer = PoolingLayer(features_dim, features_dim, 'avg', kernel_size=2, stride=2, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) else: pool_layer = PoolingLayer(features_dim, features_dim, 'max', kernel_size=2, stride=2, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) transition_layers = [conv_layer, pool_layer] elif downsample_type == 'conv': conv_layer = ConvLayer(features_dim, 2 * features_dim, kernel_size=3, stride=2, use_bn=True, act_func='relu', dropout_rate=dropout_rate, ops_order=ops_order) features_dim *= 2 transition_layers = [conv_layer] else: raise NotImplementedError image_size //= 2 inter_block_transition = TransitionBlock(transition_layers) blocks.append(inter_block_transition) if ops_order == 'weight_bn_act': global_avg_pool = PoolingLayer(features_dim, features_dim, 'avg', kernel_size=image_size, stride=image_size, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) elif ops_order == 'act_weight_bn': global_avg_pool = PoolingLayer(features_dim, features_dim, 'avg', kernel_size=image_size, stride=image_size, use_bn=False, act_func='relu', dropout_rate=0, ops_order=ops_order) elif ops_order == 'bn_act_weight': global_avg_pool = PoolingLayer(features_dim, features_dim, 'avg', kernel_size=image_size, stride=image_size, use_bn=True, act_func='relu', dropout_rate=0, ops_order=ops_order) else: raise NotImplementedError transition2classes = TransitionBlock([global_avg_pool]) blocks.append(transition2classes) classifier = LinearLayer(features_dim, n_classes, bias=True) tree_node_config = { 'use_avg': True, 'bn_before_add': False, 'path_drop_rate': path_drop_rate, 'use_zero_drop': use_zero_drop, 'drop_only_add': drop_only_add, } return ChainNet(blocks, classifier, ops_order, tree_node_config)
def set_standard_net(data_shape, n_classes, growth_rate, dense_block_per_group, total_groups, downsample_type, first_ratio=2, reduction=0.5, bottleneck=4, final_bn=False, no_first_relu=False, use_depth_sep_conv=False, groups_3x3=1, ops_order='bn_act_weight', dropout_rate=0, path_drop_rate=0, use_zero_drop=True, drop_only_add=False): image_channel, image_size = data_shape[0:2] features_dim = growth_rate * first_ratio if ops_order == 'weight_bn_act': init_conv_layer = ConvLayer(image_channel, features_dim, kernel_size=3, use_bn=True, act_func='relu', dropout_rate=0, ops_order=ops_order) elif ops_order == 'act_weight_bn': init_conv_layer = ConvLayer(image_channel, features_dim, kernel_size=3, use_bn=True, act_func=None, dropout_rate=0, ops_order=ops_order) elif ops_order == 'bn_act_weight': init_conv_layer = ConvLayer(image_channel, features_dim, kernel_size=3, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) else: raise NotImplementedError if final_bn: init_bn_layer = IdentityLayer(features_dim, features_dim, use_bn=True, act_func=None, dropout_rate=0, ops_order=ops_order) transition2blocks = TransitionBlock( [init_conv_layer, init_bn_layer]) else: transition2blocks = TransitionBlock([init_conv_layer]) blocks = [transition2blocks] for group_idx in range(total_groups): for block_idx in range(dense_block_per_group): if no_first_relu: in_bottle = ConvLayer(features_dim, growth_rate * bottleneck, kernel_size=1, use_bn=True, act_func=None, dropout_rate=dropout_rate, ops_order=ops_order) else: in_bottle = ConvLayer(features_dim, growth_rate * bottleneck, kernel_size=1, use_bn=True, act_func='relu', dropout_rate=dropout_rate, ops_order=ops_order) if use_depth_sep_conv: cell_edge = DepthConvLayer(growth_rate * bottleneck, growth_rate, kernel_size=3, use_bn=True, act_func='relu', dropout_rate=dropout_rate, ops_order=ops_order) else: cell_edge = ConvLayer(growth_rate * bottleneck, growth_rate, kernel_size=3, groups=groups_3x3, use_bn=True, act_func='relu', dropout_rate=dropout_rate, ops_order=ops_order) cell = TreeNode(child_nodes=[None], edges=[cell_edge], in_channels=growth_rate * bottleneck, out_channels=growth_rate, split_type=None, merge_type=None, path_drop_rate=path_drop_rate, use_zero_drop=use_zero_drop, drop_only_add=drop_only_add) dense_block = DenseBlock(cell, in_bottle, out_bottle=None, final_bn=final_bn) blocks.append(dense_block) features_dim += growth_rate if group_idx != total_groups - 1: if downsample_type == 'pool': pool_layer = PoolingLayer(features_dim, features_dim, 'avg', kernel_size=2, stride=2, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) transition_layers = [pool_layer] elif downsample_type == 'conv-pool': conv_layer = ConvLayer(features_dim, int(features_dim * reduction), kernel_size=1, stride=1, use_bn=True, act_func='relu', dropout_rate=dropout_rate, ops_order=ops_order) features_dim = int(features_dim * reduction) pool_layer = PoolingLayer(features_dim, features_dim, 'avg', kernel_size=2, stride=2, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) transition_layers = [conv_layer, pool_layer] else: raise NotImplementedError image_size //= 2 inter_block_transition = TransitionBlock(transition_layers) blocks.append(inter_block_transition) if ops_order == 'weight_bn_act': global_avg_pool = PoolingLayer(features_dim, features_dim, 'avg', kernel_size=image_size, stride=image_size, use_bn=False, act_func=None, dropout_rate=0, ops_order=ops_order) elif ops_order == 'act_weight_bn': global_avg_pool = PoolingLayer(features_dim, features_dim, 'avg', kernel_size=image_size, stride=image_size, use_bn=False, act_func='relu', dropout_rate=0, ops_order=ops_order) elif ops_order == 'bn_act_weight': global_avg_pool = PoolingLayer(features_dim, features_dim, 'avg', kernel_size=image_size, stride=image_size, use_bn=True, act_func='relu', dropout_rate=0, ops_order=ops_order) else: raise NotImplementedError transition2classes = TransitionBlock([global_avg_pool]) blocks.append(transition2classes) classifier = LinearLayer(features_dim, n_classes, bias=True) tree_node_config = { 'use_avg': True, 'bn_before_add': False, 'path_drop_rate': path_drop_rate, 'use_zero_drop': use_zero_drop, 'drop_only_add': drop_only_add, } return DenseNet(blocks, classifier, ops_order, tree_node_config)