Exemple #1
0
    def __init__(self,
                 classes: Sequence[str],
                 input_len: Optional[int] = None,
                 config: Optional[ED] = None) -> NoReturn:
        """ finished, checked,

        Parameters:
        -----------
        classes: list,
            list of the classes for classification
        input_len: int, optional,
            sequence length (last dim.) of the input,
            defaults to `TrainCfg.input_len`,
            will not be used in the inference mode
        config: dict, optional,
            other hyper-parameters, including kernel sizes, etc.
            ref. the corresponding config file
        """
        super().__init__()
        self.classes = list(classes)
        self.n_classes = len(classes)
        self.n_leads = 12
        self.input_len = input_len or TrainCfg.input_len
        self.config = deepcopy(ECG_CRNN_CONFIG)
        self.config.update(deepcopy(config) or {})
        if self.__DEBUG__:
            print(
                f"classes (totally {self.n_classes}) for prediction:{self.classes}"
            )
            print(
                f"configuration of {self.__name__} is as follows\n{dict_to_str(self.config)}"
            )

        cnn_choice = self.config.cnn.name.lower()
        if "vgg16" in cnn_choice:
            self.cnn = VGG16(self.n_leads, **(self.config.cnn[cnn_choice]))
            rnn_input_size = self.config.cnn.vgg16.num_filters[-1]
        elif "resnet" in cnn_choice:
            self.cnn = ResNet(self.n_leads, **(self.config.cnn[cnn_choice]))
            rnn_input_size = \
                2**len(self.config.cnn.resnet.num_blocks) * self.config.cnn.resnet.init_num_filters
        else:
            raise NotImplementedError
        # self.cnn_output_len = cnn_output_shape[2]
        if self.__DEBUG__:
            cnn_output_shape = self.cnn.compute_output_shape(self.input_len,
                                                             batch_size=None)
            print(
                f"cnn output shape (batch_size, features, seq_len) = {cnn_output_shape}"
            )

        if self.config.rnn.name.lower() == 'none':
            self.rnn = None
            _, clf_input_size, _ = self.cnn.compute_output_shape(
                self.input_len, batch_size=None)
            self.max_pool = nn.AdaptiveMaxPool1d((1, ), return_indices=False)
        elif self.config.rnn.name.lower() == 'lstm':
            hidden_sizes = self.config.rnn.lstm.hidden_sizes + [self.n_classes]
            if self.__DEBUG__:
                print(
                    f"lstm hidden sizes {self.config.rnn.lstm.hidden_sizes} ---> {hidden_sizes}"
                )
            self.rnn = StackedLSTM(
                input_size=rnn_input_size,
                hidden_sizes=hidden_sizes,
                bias=self.config.rnn.lstm.bias,
                dropouts=self.config.rnn.lstm.dropouts,
                bidirectional=self.config.rnn.lstm.bidirectional,
                return_sequences=self.config.rnn.lstm.retseq,
                # nonlinearity=self.config.rnn.lstm.nonlinearity,
            )
            if self.config.rnn.lstm.retseq:
                self.max_pool = nn.AdaptiveMaxPool1d((1, ),
                                                     return_indices=False)
            else:
                self.max_pool = None
            clf_input_size = self.rnn.compute_output_shape(None, None)[-1]
        elif self.config.rnn.name.lower() == 'attention':
            hidden_sizes = self.config.rnn.attention.hidden_sizes
            attn_in_channels = hidden_sizes[-1]
            if self.config.rnn.attention.bidirectional:
                attn_in_channels *= 2
            self.rnn = nn.Sequential(
                StackedLSTM(
                    input_size=rnn_input_size,
                    hidden_sizes=hidden_sizes,
                    bias=self.config.rnn.attention.bias,
                    dropouts=self.config.rnn.attention.dropouts,
                    bidirectional=self.config.rnn.attention.bidirectional,
                    return_sequences=True,
                    # nonlinearity=self.config.rnn.attention.nonlinearity,
                ),
                SelfAttention(
                    in_features=attn_in_channels,
                    head_num=self.config.rnn.attention.head_num,
                    dropout=self.config.rnn.attention.dropout,
                    bias=self.config.rnn.attention.bias,
                ))
            self.max_pool = nn.AdaptiveMaxPool1d((1, ), return_indices=False)
            clf_input_size = self.rnn[-1].compute_output_shape(None, None)[-1]
        else:
            raise NotImplementedError

        if self.__DEBUG__:
            print(f"clf_input_size = {clf_input_size}")

        # input of `self.clf` has shape: batch_size, channels
        self.clf = nn.Linear(clf_input_size, self.n_classes)
        self.sigmoid = nn.Sigmoid()  # for making inference
Exemple #2
0
    def __init__(self, classes: Sequence[str], config: dict) -> NoReturn:
        """ finished, checked,

        Parameters:
        -----------
        classes: list,
            list of the classes for sequence labeling
        config: dict, optional,
            other hyper-parameters, including kernel sizes, etc.
            ref. the corresponding config file
        """
        super().__init__()
        self.classes = list(classes)
        self.n_classes = len(classes)
        self.n_leads = 12
        self.config = ED(deepcopy(config))
        if self.__DEBUG__:
            print(
                f"classes (totally {self.n_classes}) for prediction:{self.classes}"
            )
            print(
                f"configuration of {self.__name__} is as follows\n{dict_to_str(self.config)}"
            )
        __debug_seq_len = 4000

        # currently, the CNN part only uses `MultiScopicCNN`
        # can be 'multi_scopic' or 'multi_scopic_leadwise'
        cnn_choice = self.config.cnn.name.lower()
        self.cnn = MultiScopicCNN(self.n_leads,
                                  **(self.config.cnn[cnn_choice]))
        rnn_input_size = self.cnn.compute_output_shape(__debug_seq_len,
                                                       batch_size=None)[1]

        if self.__DEBUG__:
            cnn_output_shape = self.cnn.compute_output_shape(__debug_seq_len,
                                                             batch_size=None)
            print(
                f"cnn output shape (batch_size, features, seq_len) = {cnn_output_shape}, given input seq_len = {__debug_seq_len}"
            )
            __debug_seq_len = cnn_output_shape[-1]

        if self.config.rnn.name.lower() == 'none':
            self.rnn = None
            attn_input_size = rnn_input_size
        elif self.config.rnn.name.lower() == 'lstm':
            self.rnn = StackedLSTM(
                input_size=rnn_input_size,
                hidden_sizes=self.config.rnn.lstm.hidden_sizes,
                bias=self.config.rnn.lstm.bias,
                dropout=self.config.rnn.lstm.dropout,
                bidirectional=self.config.rnn.lstm.bidirectional,
                return_sequences=True,
            )
            attn_input_size = self.rnn.compute_output_shape(None, None)[-1]
        else:
            raise NotImplementedError

        if self.__DEBUG__:
            if self.rnn:
                rnn_output_shape = self.rnn.compute_output_shape(
                    __debug_seq_len, batch_size=None)
            print(
                f"rnn output shape (seq_len, batch_size, features) = {rnn_output_shape}, given input seq_len = {__debug_seq_len}"
            )

        self.pool = nn.AdaptiveAvgPool1d((1, ))

        self.attn = nn.Sequential()
        attn_out_channels = self.config.attn.out_channels + [attn_input_size]
        self.attn.add_module(
            "attn",
            SeqLin(
                in_channels=attn_input_size,
                out_channels=attn_out_channels,
                activation=self.config.attn.activation,
                bias=self.config.attn.bias,
                kernel_initializer=self.config.attn.kernel_initializer,
                dropouts=self.config.attn.dropouts,
            ))
        self.attn.add_module("softmax", nn.Softmax(-1))

        if self.__DEBUG__:
            print(f"")

        clf_input_size = self.config.attn.out_channels[-1]
        clf_out_channels = self.config.clf.out_channels + [self.n_classes]
        self.clf = SeqLin(
            in_channels=clf_input_size,
            out_channels=clf_out_channels,
            activation=self.config.clf.activation,
            bias=self.config.clf.bias,
            kernel_initializer=self.config.clf.kernel_initializer,
            dropouts=self.config.clf.dropouts,
            skip_last_activation=True,
        )

        # sigmoid for inference
        self.softmax = nn.Softmax(-1)
Exemple #3
0
class ECG_CRNN(nn.Module):
    """

    C(R)NN models modified from the following refs.

    References:
    -----------
    [1] Yao, Qihang, et al. "Time-Incremental Convolutional Neural Network for Arrhythmia Detection in Varied-Length Electrocardiogram." 2018 IEEE 16th Intl Conf on Dependable, Autonomic and Secure Computing, 16th Intl Conf on Pervasive Intelligence and Computing, 4th Intl Conf on Big Data Intelligence and Computing and Cyber Science and Technology Congress (DASC/PiCom/DataCom/CyberSciTech). IEEE, 2018.
    [2] Yao, Qihang, et al. "Multi-class Arrhythmia detection from 12-lead varied-length ECG using Attention-based Time-Incremental Convolutional Neural Network." Information Fusion 53 (2020): 174-182.
    [3] Hannun, Awni Y., et al. "Cardiologist-level arrhythmia detection and classification in ambulatory electrocardiograms using a deep neural network." Nature medicine 25.1 (2019): 65.
    [4] https://stanfordmlgroup.github.io/projects/ecg2/
    [5] https://github.com/awni/ecg
    [6] CPSC2018 entry 0236
    """
    __DEBUG__ = True
    __name__ = 'ECG_CRNN'

    def __init__(self,
                 classes: Sequence[str],
                 input_len: Optional[int] = None,
                 config: Optional[ED] = None) -> NoReturn:
        """ finished, checked,

        Parameters:
        -----------
        classes: list,
            list of the classes for classification
        input_len: int, optional,
            sequence length (last dim.) of the input,
            defaults to `TrainCfg.input_len`,
            will not be used in the inference mode
        config: dict, optional,
            other hyper-parameters, including kernel sizes, etc.
            ref. the corresponding config file
        """
        super().__init__()
        self.classes = list(classes)
        self.n_classes = len(classes)
        self.n_leads = 12
        self.input_len = input_len or TrainCfg.input_len
        self.config = deepcopy(ECG_CRNN_CONFIG)
        self.config.update(deepcopy(config) or {})
        if self.__DEBUG__:
            print(
                f"classes (totally {self.n_classes}) for prediction:{self.classes}"
            )
            print(
                f"configuration of {self.__name__} is as follows\n{dict_to_str(self.config)}"
            )

        cnn_choice = self.config.cnn.name.lower()
        if "vgg16" in cnn_choice:
            self.cnn = VGG16(self.n_leads, **(self.config.cnn[cnn_choice]))
            rnn_input_size = self.config.cnn.vgg16.num_filters[-1]
        elif "resnet" in cnn_choice:
            self.cnn = ResNet(self.n_leads, **(self.config.cnn[cnn_choice]))
            rnn_input_size = \
                2**len(self.config.cnn.resnet.num_blocks) * self.config.cnn.resnet.init_num_filters
        else:
            raise NotImplementedError
        # self.cnn_output_len = cnn_output_shape[2]
        if self.__DEBUG__:
            cnn_output_shape = self.cnn.compute_output_shape(self.input_len,
                                                             batch_size=None)
            print(
                f"cnn output shape (batch_size, features, seq_len) = {cnn_output_shape}"
            )

        if self.config.rnn.name.lower() == 'none':
            self.rnn = None
            _, clf_input_size, _ = self.cnn.compute_output_shape(
                self.input_len, batch_size=None)
            self.max_pool = nn.AdaptiveMaxPool1d((1, ), return_indices=False)
        elif self.config.rnn.name.lower() == 'lstm':
            hidden_sizes = self.config.rnn.lstm.hidden_sizes + [self.n_classes]
            if self.__DEBUG__:
                print(
                    f"lstm hidden sizes {self.config.rnn.lstm.hidden_sizes} ---> {hidden_sizes}"
                )
            self.rnn = StackedLSTM(
                input_size=rnn_input_size,
                hidden_sizes=hidden_sizes,
                bias=self.config.rnn.lstm.bias,
                dropouts=self.config.rnn.lstm.dropouts,
                bidirectional=self.config.rnn.lstm.bidirectional,
                return_sequences=self.config.rnn.lstm.retseq,
                # nonlinearity=self.config.rnn.lstm.nonlinearity,
            )
            if self.config.rnn.lstm.retseq:
                self.max_pool = nn.AdaptiveMaxPool1d((1, ),
                                                     return_indices=False)
            else:
                self.max_pool = None
            clf_input_size = self.rnn.compute_output_shape(None, None)[-1]
        elif self.config.rnn.name.lower() == 'attention':
            hidden_sizes = self.config.rnn.attention.hidden_sizes
            attn_in_channels = hidden_sizes[-1]
            if self.config.rnn.attention.bidirectional:
                attn_in_channels *= 2
            self.rnn = nn.Sequential(
                StackedLSTM(
                    input_size=rnn_input_size,
                    hidden_sizes=hidden_sizes,
                    bias=self.config.rnn.attention.bias,
                    dropouts=self.config.rnn.attention.dropouts,
                    bidirectional=self.config.rnn.attention.bidirectional,
                    return_sequences=True,
                    # nonlinearity=self.config.rnn.attention.nonlinearity,
                ),
                SelfAttention(
                    in_features=attn_in_channels,
                    head_num=self.config.rnn.attention.head_num,
                    dropout=self.config.rnn.attention.dropout,
                    bias=self.config.rnn.attention.bias,
                ))
            self.max_pool = nn.AdaptiveMaxPool1d((1, ), return_indices=False)
            clf_input_size = self.rnn[-1].compute_output_shape(None, None)[-1]
        else:
            raise NotImplementedError

        if self.__DEBUG__:
            print(f"clf_input_size = {clf_input_size}")

        # input of `self.clf` has shape: batch_size, channels
        self.clf = nn.Linear(clf_input_size, self.n_classes)
        self.sigmoid = nn.Sigmoid()  # for making inference

    def forward(self, input: Tensor) -> Tensor:
        """ finished, partly checked (rnn part might have bugs),

        input: of shape (batch_size, channels, seq_len)
        output: of shape (batch_size, n_classes)
        """
        x = self.cnn(input)  # batch_size, channels, seq_len
        # print(f"cnn out shape = {x.shape}")
        if self.rnn:
            # (batch_size, channels, seq_len) -> (seq_len, batch_size, input_size)
            x = x.permute(2, 0, 1)
            x = self.rnn(x)
            if self.max_pool:
                # (seq_len, batch_size, channels) -> (batch_size, channels, seq_len)
                x = x.permute(1, 2, 0)
                x = self.max_pool(x)  # (batch_size, channels, 1)
                # x = torch.flatten(x, start_dim=1)  # (batch_size, channels)
                x = x.squeeze(dim=-1)
            else:
                # x of shape (batch_size, channels)
                pass
            # print(f"rnn out shape = {x.shape}")
        else:
            # (batch_size, channels, seq_len) --> (batch_size, channels)
            x = self.max_pool(x)
            # print(f"max_pool out shape = {x.shape}")
            # x = torch.flatten(x, start_dim=1)
            x = x.squeeze(dim=-1)
        # print(f"clf in shape = {x.shape}")
        pred = self.clf(x)  # batch_size, n_classes
        return pred

    @torch.no_grad()
    def inference(
        self,
        input: Tensor,
        class_names: bool = False,
        bin_pred_thr: float = 0.5
    ) -> Tuple[Union[np.ndarray, pd.DataFrame], np.ndarray]:
        """ finished, checked,

        auxiliary function to `forward`,

        Parameters:
        -----------
        input: Tensor,
            input tensor, of shape (batch_size, channels, seq_len)
        class_names: bool, default False,
            if True, the returned scalar predictions will be a `DataFrame`,
            with class names for each scalar prediction
        bin_pred_thr: float, default 0.5,
            the threshold for making binary predictions from scalar predictions

        Returns:
        --------
        pred: ndarray or DataFrame,
            scalar predictions, (and binary predictions if `class_names` is True)
        bin_pred: ndarray,
            the array (with values 0, 1 for each class) of binary prediction
        """
        if "NSR" in self.classes:
            nsr_cid = self.classes.index("NSR")
        elif "426783006" in self.classes:
            nsr_cid = self.classes.index("426783006")
        else:
            nsr_cid = None
        pred = self.forward(input)
        pred = self.sigmoid(pred)
        bin_pred = (pred >= bin_pred_thr).int()
        pred = pred.cpu().detach().numpy()
        bin_pred = bin_pred.cpu().detach().numpy()
        for row_idx, row in enumerate(bin_pred):
            row_max_prob = pred[row_idx, ...].max()
            if row_max_prob < ModelCfg.bin_pred_nsr_thr and nsr_cid is not None:
                bin_pred[row_idx, nsr_cid] = 1
            elif row.sum() == 0:
                bin_pred[row_idx,...] = \
                    (((pred[row_idx,...]+ModelCfg.bin_pred_look_again_tol) >= row_max_prob) & (pred[row_idx,...] >= ModelCfg.bin_pred_nsr_thr)).astype(int)
        if class_names:
            pred = pd.DataFrame(pred)
            pred.columns = self.classes
            # pred['bin_pred'] = pred.apply(
            #     lambda row: np.array(self.classes)[np.where(row.values>=bin_pred_thr)[0]],
            #     axis=1
            # )
            pred['bin_pred'] = ''
            for row_idx in range(len(pred)):
                pred.at[row_idx, 'bin_pred'] = \
                    np.array(self.classes)[np.where(bin_pred==1)[0]].tolist()
        return pred, bin_pred

    @property
    def module_size(self):
        """
        """
        return compute_module_size(self)
Exemple #4
0
class ECG_SEQ_LAB_NET(nn.Module):
    """ NOT finished,

    SOTA model from CPSC2019 challenge (entry 0416)

    pipeline:
    multi-scopic cnn --> (bidi-lstm -->) "attention" --> seq linear
    """
    __DEBUG__ = True
    __name__ = "ECG_SEQ_LAB_NET"

    def __init__(self, classes: Sequence[str], config: dict) -> NoReturn:
        """ finished, checked,

        Parameters:
        -----------
        classes: list,
            list of the classes for sequence labeling
        config: dict, optional,
            other hyper-parameters, including kernel sizes, etc.
            ref. the corresponding config file
        """
        super().__init__()
        self.classes = list(classes)
        self.n_classes = len(classes)
        self.n_leads = 12
        self.config = ED(deepcopy(config))
        if self.__DEBUG__:
            print(
                f"classes (totally {self.n_classes}) for prediction:{self.classes}"
            )
            print(
                f"configuration of {self.__name__} is as follows\n{dict_to_str(self.config)}"
            )
        __debug_seq_len = 4000

        # currently, the CNN part only uses `MultiScopicCNN`
        # can be 'multi_scopic' or 'multi_scopic_leadwise'
        cnn_choice = self.config.cnn.name.lower()
        self.cnn = MultiScopicCNN(self.n_leads,
                                  **(self.config.cnn[cnn_choice]))
        rnn_input_size = self.cnn.compute_output_shape(__debug_seq_len,
                                                       batch_size=None)[1]

        if self.__DEBUG__:
            cnn_output_shape = self.cnn.compute_output_shape(__debug_seq_len,
                                                             batch_size=None)
            print(
                f"cnn output shape (batch_size, features, seq_len) = {cnn_output_shape}, given input seq_len = {__debug_seq_len}"
            )
            __debug_seq_len = cnn_output_shape[-1]

        if self.config.rnn.name.lower() == 'none':
            self.rnn = None
            attn_input_size = rnn_input_size
        elif self.config.rnn.name.lower() == 'lstm':
            self.rnn = StackedLSTM(
                input_size=rnn_input_size,
                hidden_sizes=self.config.rnn.lstm.hidden_sizes,
                bias=self.config.rnn.lstm.bias,
                dropout=self.config.rnn.lstm.dropout,
                bidirectional=self.config.rnn.lstm.bidirectional,
                return_sequences=True,
            )
            attn_input_size = self.rnn.compute_output_shape(None, None)[-1]
        else:
            raise NotImplementedError

        if self.__DEBUG__:
            if self.rnn:
                rnn_output_shape = self.rnn.compute_output_shape(
                    __debug_seq_len, batch_size=None)
            print(
                f"rnn output shape (seq_len, batch_size, features) = {rnn_output_shape}, given input seq_len = {__debug_seq_len}"
            )

        self.pool = nn.AdaptiveAvgPool1d((1, ))

        self.attn = nn.Sequential()
        attn_out_channels = self.config.attn.out_channels + [attn_input_size]
        self.attn.add_module(
            "attn",
            SeqLin(
                in_channels=attn_input_size,
                out_channels=attn_out_channels,
                activation=self.config.attn.activation,
                bias=self.config.attn.bias,
                kernel_initializer=self.config.attn.kernel_initializer,
                dropouts=self.config.attn.dropouts,
            ))
        self.attn.add_module("softmax", nn.Softmax(-1))

        if self.__DEBUG__:
            print(f"")

        clf_input_size = self.config.attn.out_channels[-1]
        clf_out_channels = self.config.clf.out_channels + [self.n_classes]
        self.clf = SeqLin(
            in_channels=clf_input_size,
            out_channels=clf_out_channels,
            activation=self.config.clf.activation,
            bias=self.config.clf.bias,
            kernel_initializer=self.config.clf.kernel_initializer,
            dropouts=self.config.clf.dropouts,
            skip_last_activation=True,
        )

        # sigmoid for inference
        self.softmax = nn.Softmax(-1)

    def forward(self, input: Tensor) -> Tensor:
        """ finished, NOT checked,
        input: of shape (batch_size, channels, seq_len)
        """
        # cnn
        cnn_output = self.cnn(input)  # (batch_size, channels, seq_len)

        # rnn or none
        if self.rnn:
            rnn_output = cnn_output.permute(
                2, 0, 1)  # (seq_len, batch_size, channels)
            rnn_output = self.rnn(
                rnn_output)  # (seq_len, batch_size, channels)
            rnn_output = rnn_output.permute(
                1, 2, 0)  # (batch_size, channels, seq_len)
        else:
            rnn_output = cnn_output
        x = self.pool(rnn_output)  # (batch_size, channels, 1)
        x = x.squeeze(-1)  # (batch_size, channels)

        # attention
        x = self.attn(x)  # (batch_size, channels)
        x = x.unsqueeze(-1)  # (batch_size, channels, 1)
        x = rnn_output * x  # (batch_size, channels, seq_len)
        x = x.permute(0, 2, 1)  # (batch_size, seq_len, channels)
        ouput = self.clf(x)
        return output

    def compute_output_shape(self,
                             seq_len: int,
                             batch_size: Optional[int] = None
                             ) -> Sequence[Union[int, type(None)]]:
        """ NOT finished,

        Parameters:
        -----------
        seq_len: int,
            length of the 1d sequence
        batch_size: int, optional,
            the batch size, can be None

        Returns:
        --------
        output_shape: sequence,
            the output shape of this block, given `seq_len` and `batch_size`
        """
        _seq_len = seq_len
        output_shape = self.cnn.compute_output_shape(_seq_len, batch_size)
        _, _, _seq_len = output_shape
        if self.rnn:
            output_shape = self.rnn.compute_output_shape(_seq_len, batch_size)

    @property
    def module_size(self):
        """
        """
        module_parameters = filter(lambda p: p.requires_grad,
                                   self.parameters())
        n_params = sum([np.prod(p.size()) for p in module_parameters])
        return n_params