• 推荐系统(三):基于pytorch实现DCN


    一、main.py

    import torch
    import tqdm
    from sklearn.metrics import roc_auc_score
    from torch.utils.data import DataLoader
    import os
    import numpy as np
    from torchfm.dataset.criteo import CriteoDataset
    
    from torchfm.model.dcn import DeepCrossNetworkModel
    
    from torchfm.model.fnn import FactorizationSupportedNeuralNetworkModel
    
    from torchfm.model.pnn import ProductNeuralNetworkModel
    
    
    
    def get_dataset(path):
        return CriteoDataset(path)
    
    
    
    def get_model(name, dataset):
        """
        Hyperparameters are empirically determined, not opitmized.
        """
        field_dims = dataset.field_dims
        if name == 'fnn':
            return FactorizationSupportedNeuralNetworkModel(field_dims, embed_dim=16, mlp_dims=(16, 16), dropout=0.2)
        elif name == 'ipnn':
            return ProductNeuralNetworkModel(field_dims, embed_dim=16, mlp_dims=(16,), method='inner', dropout=0.2)
        elif name == 'opnn':
            return ProductNeuralNetworkModel(field_dims, embed_dim=16, mlp_dims=(16,), method='outer', dropout=0.2)
        elif name == 'dcn':
            return DeepCrossNetworkModel(field_dims, embed_dim=16, num_layers=3, mlp_dims=(16, 16), dropout=0.2)
    
        else:
            raise ValueError('unknown model name: ' + name)
    
    
    class EarlyStopper(object):
    
        def __init__(self, num_trials, save_path):
            self.num_trials = num_trials
            self.trial_counter = 0
            self.best_accuracy = 0
            self.best_loss = 100
            self.save_path = save_path
    
        def is_continuable(self, model, accuracy):
            if accuracy > self.best_accuracy:
                self.best_accuracy = accuracy
                self.trial_counter = 0
                torch.save(model, self.save_path)
                return True
            elif self.trial_counter + 1 < self.num_trials:
                self.trial_counter += 1
                return True
            else:
                return False
    
        def is_continue_loss(self, model, loss):
            if loss < self.best_loss:
                self.best_loss = loss
                self.trial_counter = 0
                torch.save(model, self.save_path)
                return True
            elif self.trial_counter + 1 < self.num_trials:
                self.trial_counter += 1
                return True
            else:
                return False
    
    
    def train(model, optimizer, data_loader, criterion, device, log_interval=100):
        model.train()
        total_loss = 0
        tk0 = tqdm.tqdm(data_loader, smoothing=0, mininterval=1.0)
        for i, (fields, target) in enumerate(tk0):
            fields, target = fields.to(device), target.to(device)
            y = model(fields)
            loss = criterion(y, target.float())
            model.zero_grad()
            loss.backward()
            optimizer.step()
            total_loss += loss.item()
            if (i + 1) % log_interval == 0:
                tk0.set_postfix(loss=total_loss / log_interval)
                total_loss = 0
    
    
    def my_test(model, data_loader, device, criterion):
        model.eval()
        targets, predicts = list(), list()
        with torch.no_grad():
            for fields, target in tqdm.tqdm(data_loader, smoothing=0, mininterval=1.0):
                fields, target = fields.to(device), target.to(device)
                y = model(fields)
                targets.extend(target.tolist())
                predicts.extend(y.tolist())
                loss = criterion(y, target.float())
        return loss, roc_auc_score(targets, predicts)
    
    
    
    def main(dataset_path,
             model_name,
             epoch,
             learning_rate,
             batch_size,
             weight_decay,
             device,
             save_dir):
        device = torch.device(device)
        dataset = get_dataset(dataset_path)
        dims = dataset.field_dims
        npy_out = "field_dims.npy"
        if not os.path.exists(npy_out):
            np.save(npy_out, dims)
    
        train_length = int(len(dataset) * 0.8)
        valid_length = int(len(dataset) * 0.1)
        test_length = len(dataset) - train_length - valid_length
        train_dataset, valid_dataset, test_dataset = torch.utils.data.random_split(
            dataset, (train_length, valid_length, test_length))
        train_data_loader = DataLoader(train_dataset, batch_size=batch_size, num_workers=0)
        valid_data_loader = DataLoader(valid_dataset, batch_size=batch_size, num_workers=0)
        test_data_loader = DataLoader(test_dataset, batch_size=batch_size, num_workers=0)
        model = get_model(model_name, dataset).to(device)
        criterion = torch.nn.BCELoss()
        optimizer = torch.optim.Adam(params=model.parameters(), lr=learning_rate, weight_decay=weight_decay)
        early_stopper = EarlyStopper(num_trials=5, save_path=f'{save_dir}/{model_name}.pt')
        for epoch_i in range(epoch):
            train(model, optimizer, train_data_loader, criterion, device)
            loss, auc = my_test(model, valid_data_loader, device, criterion)
            print('epoch:', epoch_i, 'validation: auc:', auc, "loss:", loss)
            if not early_stopper.is_continuable(model, auc):
                print(f'validation: best auc: {early_stopper.best_accuracy}')
                break
        loss, auc = my_test(model, test_data_loader, device, criterion)
        print(f'test loss: {loss}, test auc: {auc}')
    
    
    if __name__ == '__main__':
        import argparse
    
        parser = argparse.ArgumentParser()
    
        parser.add_argument('--dataset_path', default=r'E:
    ecommend_systemdata	rain.txt')
        parser.add_argument('--model_name', default='dcn')
        parser.add_argument('--epoch', type=int, default=100)
        parser.add_argument('--learning_rate', type=float, default=0.005)
        parser.add_argument('--batch_size', type=int, default=32)
        parser.add_argument('--weight_decay', type=float, default=1e-6)
        parser.add_argument('--device', default='cpu')
        parser.add_argument('--save_dir', default='E:/recommend_system/chkpt')
        args = parser.parse_args()
        main(args.dataset_path,
             args.model_name,
             args.epoch,
             args.learning_rate,
             args.batch_size,
             args.weight_decay,
             args.device,
             args.save_dir)

    二、predicts.py

    import numpy as np
    from functools import lru_cache
    import math
    import torch
    
    @lru_cache(maxsize=None)
    def convert_numeric_feature(val: str):
        if val == '':
            return 'NULL'
        v = int(val)
        if v > 2:
            return str(int(math.log(v) ** 2))
        else:
            return str(v - 2)
    
    class InferencePredict():
        def __init__(self):
            mapper_path = "mapper.npy"
            self.model_path = r"E:
    ecommend_systemchkptdcn.pt"
            """数值和类别总列数"""
            self.NUM_FEATS = 23
            """数值类别"""
            self.NUM_INT_FEATS = 7
            read_dictionary = np.load(mapper_path, allow_pickle=True).item()
            self.feat_mapper = read_dictionary["feat_mapper"]
            self.defaults = read_dictionary["defaults"]
            self.model = torch.load(self.model_path)
            self.model.eval()
    
        def get_data(self, dataline, feat_mapper,defaults):
            values = dataline.rstrip('
    ').split('	')
            np_array = np.zeros(self.NUM_FEATS + 1, dtype=np.uint32)
            np_array[0] = int(values[0])
            for i in range(1, self.NUM_INT_FEATS + 1):
                np_array[i] = feat_mapper[i].get(convert_numeric_feature(values[i]), defaults[i])
            for i in range(self.NUM_INT_FEATS + 1, self.NUM_FEATS + 1):
                np_array[i] = feat_mapper[i].get(values[i], defaults[i])
            return np_array
    
        def predict(self, dataline):
    
            np_array = self.get_data(dataline, self.feat_mapper,self.defaults).astype(dtype=np.long)
            x,y = np_array[1:], np_array[0]
            with torch.no_grad():
                output = self.model(torch.from_numpy(x))
                print(output)
                preds = int(torch.round(output).item())
            return preds, y
    
        def main(self):
            s1 = "1    450    18    47    619    153    12    0    male    daily    1    0    0    0    0    MD280612    BR803759    PV285968    CT470265    PF470265    10    0    2    0    W441,W19887,W45818,W63,W894,W38883,W135945,W1684,W72349,W15298,W858,W38883"
            s2 = "0    290    0    34    0    0    12    5    male    monthly    1    0    0    0    0    MD441850    BR803759    PV419710    CT378940    PF470265    0    0    3    0    W605,W396,W554,W6275,W6257,W651,W51,W32265,W682,W250,W97,W1748"
            s3 = "0    290    0    34    0    0    12    5    male    monthly    1    0    0    0    0    MD441850    BR803759    PV419710    CT378940    PF470265    0    0    3    0    W605,W396,W554,W6275,W6257,W651,W51,W32265,W682,W250,W97,W1748"
            self.predict(s1)
            self.predict(s2)
            self.predict(s3)
    
    
    
    if __name__ == '__main__':
        InferencePredict().main()

    三、critio.py

    import math
    import shutil
    import struct
    from collections import defaultdict
    from functools import lru_cache
    from pathlib import Path
    
    import lmdb
    import numpy as np
    import torch.utils.data
    from tqdm import tqdm
    import os
    
    
    
    class CriteoDataset(torch.utils.data.Dataset):
        """
        Criteo Display Advertising Challenge Dataset
    
        Data prepration:
            * Remove the infrequent features (appearing in less than threshold instances) and treat them as a single feature
            * Discretize numerical values by log2 transformation which is proposed by the winner of Criteo Competition
    
        :param dataset_path: criteo train.txt path.
        :param cache_path: lmdb cache path.
        :param rebuild_cache: If True, lmdb cache is refreshed.
        :param min_threshold: infrequent feature threshold.
    
        Reference:
            https://labs.criteo.com/2014/02/kaggle-display-advertising-challenge-dataset
            https://www.csie.ntu.edu.tw/~r01922136/kaggle-2014-criteo.pdf
        """
    
        def __init__(self, dataset_path=None, cache_path='.criteo', rebuild_cache=True, min_threshold=10):
            """数值和类别总列数"""
            self.NUM_FEATS = 23
            """数值类别"""
            self.NUM_INT_FEATS = 7
            self.min_threshold = min_threshold
            if rebuild_cache or not Path(cache_path).exists():
                shutil.rmtree(cache_path, ignore_errors=True)
                if dataset_path is None:
                    raise ValueError('create cache: failed: dataset_path is None')
                self.__build_cache(dataset_path, cache_path)
            self.env = lmdb.open(cache_path, create=False, lock=False, readonly=True)
            with self.env.begin(write=False) as txn:
                self.length = txn.stat()['entries'] - 1
                self.field_dims = np.frombuffer(txn.get(b'field_dims'), dtype=np.uint32)
    
        def __getitem__(self, index):
            with self.env.begin(write=False) as txn:
                np_array = np.frombuffer(
                    txn.get(struct.pack('>I', index)), dtype=np.uint32).astype(dtype=np.long)
            return np_array[1:], np_array[0]
    
        def __len__(self):
            return self.length
    
        def __build_cache(self, path, cache_path):
            feat_mapper, defaults = self.__get_feat_mapper(path)
            my_dict = {"feat_mapper":feat_mapper, "defaults": defaults}
            my_path = "mapper.npy"
            if not os.path.exists(my_path):
                np.save(my_path, my_dict)
            with lmdb.open(cache_path, map_size=int(1e11)) as env:
                field_dims = np.zeros(self.NUM_FEATS, dtype=np.uint32)
                for i, fm in feat_mapper.items():
                    field_dims[i - 1] = len(fm) + 1
                with env.begin(write=True) as txn:
                    txn.put(b'field_dims', field_dims.tobytes())
                for buffer in self.__yield_buffer(path, feat_mapper, defaults):
                    with env.begin(write=True) as txn:
                        for key, value in buffer:
                            txn.put(key, value)
    
        def __get_feat_mapper(self, path):
            feat_cnts = defaultdict(lambda: defaultdict(int))
            with open(path) as f:
                pbar = tqdm(f, mininterval=1, smoothing=0.1)
                pbar.set_description('Create criteo dataset cache: counting features')
                for line in pbar:
                    values = line.rstrip('
    ').split('	')
                    for i in range(1, self.NUM_INT_FEATS + 1):
                        feat_cnts[i][convert_numeric_feature(values[i])] += 1
                    for i in range(self.NUM_INT_FEATS + 1, self.NUM_FEATS + 1):
                        feat_cnts[i][values[i]] += 1
            feat_mapper = {i: {feat for feat, c in cnt.items() if c >= self.min_threshold} for i, cnt in feat_cnts.items()}
            feat_mapper = {i: {feat: idx for idx, feat in enumerate(cnt)} for i, cnt in feat_mapper.items()}
            defaults = {i: len(cnt) for i, cnt in feat_mapper.items()}
            return feat_mapper, defaults
    
        def __yield_buffer(self, path, feat_mapper, defaults, buffer_size=int(1e5)):
            item_idx = 0
            buffer = list()
            with open(path) as f:
                pbar = tqdm(f, mininterval=1, smoothing=0.1)
                pbar.set_description('Create criteo dataset cache: setup lmdb')
                for line in pbar:
                    values = line.rstrip('
    ').split('	')
                    np_array = np.zeros(self.NUM_FEATS + 1, dtype=np.uint32)
                    np_array[0] = int(values[0])
                    for i in range(1, self.NUM_INT_FEATS + 1):
                        np_array[i] = feat_mapper[i].get(convert_numeric_feature(values[i]), defaults[i])
                    for i in range(self.NUM_INT_FEATS + 1, self.NUM_FEATS + 1):
                        np_array[i] = feat_mapper[i].get(values[i], defaults[i])
    
                    buffer.append((struct.pack('>I', item_idx), np_array.tobytes()))
                    item_idx += 1
                    if item_idx % buffer_size == 0:
                        yield buffer
                        buffer.clear()
                yield buffer
    
    
    @lru_cache(maxsize=None)
    def convert_numeric_feature(val: str):
        if val == '':
            return 'NULL'
        v = int(val)
        if v > 2:
            return str(int(math.log(v) ** 2))
        else:
            return str(v - 2)

    四、DCN.py

    import torch
    
    from torchfm.layer import FeaturesEmbedding, CrossNetwork, MultiLayerPerceptron
    
    
    class DeepCrossNetworkModel(torch.nn.Module):
        """
        A pytorch implementation of Deep & Cross Network.
    
        Reference:
            R Wang, et al. Deep & Cross Network for Ad Click Predictions, 2017.
        """
    
        def __init__(self, field_dims, embed_dim, num_layers, mlp_dims, dropout):
            super().__init__()
            self.embedding = FeaturesEmbedding(field_dims, embed_dim)
            self.embed_output_dim = len(field_dims) * embed_dim
            self.cn = CrossNetwork(self.embed_output_dim, num_layers)
            self.mlp = MultiLayerPerceptron(self.embed_output_dim, mlp_dims, dropout, output_layer=False)
            self.linear = torch.nn.Linear(mlp_dims[-1] + self.embed_output_dim, 1)
    
        def forward(self, x):
            """
            :param x: Long tensor of size ``(batch_size, num_fields)``
            """
            embed_x = self.embedding(x).view(-1, self.embed_output_dim)
            x_l1 = self.cn(embed_x)
            h_l2 = self.mlp(embed_x)
            x_stack = torch.cat([x_l1, h_l2], dim=1)
            p = self.linear(x_stack)
            return torch.sigmoid(p.squeeze(1))

    五、layer.py

    import numpy as np
    import torch
    import torch.nn.functional as F
    
    
    class FeaturesLinear(torch.nn.Module):
    
        def __init__(self, field_dims, output_dim=1):
            super().__init__()
            self.fc = torch.nn.Embedding(sum(field_dims), output_dim)
            self.bias = torch.nn.Parameter(torch.zeros((output_dim,)))
            self.offsets = np.array((0, *np.cumsum(field_dims)[:-1]), dtype=np.long)
    
        def forward(self, x):
            """
            :param x: Long tensor of size ``(batch_size, num_fields)``
            """
            x = x + x.new_tensor(self.offsets).unsqueeze(0)
            return torch.sum(self.fc(x), dim=1) + self.bias
    
    
    class FeaturesEmbedding(torch.nn.Module):
    
        def __init__(self, field_dims, embed_dim):
            super().__init__()
            self.embedding = torch.nn.Embedding(sum(field_dims), embed_dim)
            self.offsets = np.array((0, *np.cumsum(field_dims)[:-1]), dtype=np.long)
            torch.nn.init.xavier_uniform_(self.embedding.weight.data)
    
        def forward(self, x):
            """
            :param x: Long tensor of size ``(batch_size, num_fields)``
            """
            x = x + x.new_tensor(self.offsets).unsqueeze(0)
            return self.embedding(x)
    
    
    class FieldAwareFactorizationMachine(torch.nn.Module):
    
        def __init__(self, field_dims, embed_dim):
            super().__init__()
            self.num_fields = len(field_dims)
            self.embeddings = torch.nn.ModuleList([
                torch.nn.Embedding(sum(field_dims), embed_dim) for _ in range(self.num_fields)
            ])
            self.offsets = np.array((0, *np.cumsum(field_dims)[:-1]), dtype=np.long)
            for embedding in self.embeddings:
                torch.nn.init.xavier_uniform_(embedding.weight.data)
    
        def forward(self, x):
            """
            :param x: Long tensor of size ``(batch_size, num_fields)``
            """
            x = x + x.new_tensor(self.offsets).unsqueeze(0)
            xs = [self.embeddings[i](x) for i in range(self.num_fields)]
            ix = list()
            for i in range(self.num_fields - 1):
                for j in range(i + 1, self.num_fields):
                    ix.append(xs[j][:, i] * xs[i][:, j])
            ix = torch.stack(ix, dim=1)
            return ix
    
    
    class FactorizationMachine(torch.nn.Module):
    
        def __init__(self, reduce_sum=True):
            super().__init__()
            self.reduce_sum = reduce_sum
    
        def forward(self, x):
            """
            :param x: Float tensor of size ``(batch_size, num_fields, embed_dim)``
            """
            square_of_sum = torch.sum(x, dim=1) ** 2
            sum_of_square = torch.sum(x ** 2, dim=1)
            ix = square_of_sum - sum_of_square
            if self.reduce_sum:
                ix = torch.sum(ix, dim=1, keepdim=True)
            return 0.5 * ix
    
    
    class MultiLayerPerceptron(torch.nn.Module):
    
        def __init__(self, input_dim, embed_dims, dropout, output_layer=True):
            super().__init__()
            layers = list()
            for embed_dim in embed_dims:
                layers.append(torch.nn.Linear(input_dim, embed_dim))
                layers.append(torch.nn.BatchNorm1d(embed_dim))
                layers.append(torch.nn.ReLU())
                layers.append(torch.nn.Dropout(p=dropout))
                input_dim = embed_dim
            if output_layer:
                layers.append(torch.nn.Linear(input_dim, 1))
            self.mlp = torch.nn.Sequential(*layers)
    
        def forward(self, x):
            """
            :param x: Float tensor of size ``(batch_size, embed_dim)``
            """
            return self.mlp(x)
    
    
    class InnerProductNetwork(torch.nn.Module):
    
        def forward(self, x):
            """
            :param x: Float tensor of size ``(batch_size, num_fields, embed_dim)``
            """
            num_fields = x.shape[1]
            row, col = list(), list()
            for i in range(num_fields - 1):
                for j in range(i + 1, num_fields):
                    row.append(i), col.append(j)
            return torch.sum(x[:, row] * x[:, col], dim=2)
    
    
    class OuterProductNetwork(torch.nn.Module):
    
        def __init__(self, num_fields, embed_dim, kernel_type='mat'):
            super().__init__()
            num_ix = num_fields * (num_fields - 1) // 2
            if kernel_type == 'mat':
                kernel_shape = embed_dim, num_ix, embed_dim
            elif kernel_type == 'vec':
                kernel_shape = num_ix, embed_dim
            elif kernel_type == 'num':
                kernel_shape = num_ix, 1
            else:
                raise ValueError('unknown kernel type: ' + kernel_type)
            self.kernel_type = kernel_type
            self.kernel = torch.nn.Parameter(torch.zeros(kernel_shape))
            torch.nn.init.xavier_uniform_(self.kernel.data)
    
        def forward(self, x):
            """
            :param x: Float tensor of size ``(batch_size, num_fields, embed_dim)``
            """
            num_fields = x.shape[1]
            row, col = list(), list()
            for i in range(num_fields - 1):
                for j in range(i + 1, num_fields):
                    row.append(i), col.append(j)
            p, q = x[:, row], x[:, col]
            if self.kernel_type == 'mat':
                kp = torch.sum(p.unsqueeze(1) * self.kernel, dim=-1).permute(0, 2, 1)
                return torch.sum(kp * q, -1)
            else:
                return torch.sum(p * q * self.kernel.unsqueeze(0), -1)
    
    
    class CrossNetwork(torch.nn.Module):
    
        def __init__(self, input_dim, num_layers):
            super().__init__()
            self.num_layers = num_layers
            self.w = torch.nn.ModuleList([
                torch.nn.Linear(input_dim, 1, bias=False) for _ in range(num_layers)
            ])
            self.b = torch.nn.ParameterList([
                torch.nn.Parameter(torch.zeros((input_dim,))) for _ in range(num_layers)
            ])
    
        def forward(self, x):
            """
            :param x: Float tensor of size ``(batch_size, num_fields, embed_dim)``
            """
            x0 = x
            for i in range(self.num_layers):
                xw = self.w[i](x)
                x = x0 * xw + self.b[i] + x
            return x
    
    
    class AttentionalFactorizationMachine(torch.nn.Module):
    
        def __init__(self, embed_dim, attn_size, dropouts):
            super().__init__()
            self.attention = torch.nn.Linear(embed_dim, attn_size)
            self.projection = torch.nn.Linear(attn_size, 1)
            self.fc = torch.nn.Linear(embed_dim, 1)
            self.dropouts = dropouts
    
        def forward(self, x):
            """
            :param x: Float tensor of size ``(batch_size, num_fields, embed_dim)``
            """
            num_fields = x.shape[1]
            row, col = list(), list()
            for i in range(num_fields - 1):
                for j in range(i + 1, num_fields):
                    row.append(i), col.append(j)
            p, q = x[:, row], x[:, col]
            inner_product = p * q
            attn_scores = F.relu(self.attention(inner_product))
            attn_scores = F.softmax(self.projection(attn_scores), dim=1)
            attn_scores = F.dropout(attn_scores, p=self.dropouts[0], training=self.training)
            attn_output = torch.sum(attn_scores * inner_product, dim=1)
            attn_output = F.dropout(attn_output, p=self.dropouts[1], training=self.training)
            return self.fc(attn_output)
    
    
    class CompressedInteractionNetwork(torch.nn.Module):
    
        def __init__(self, input_dim, cross_layer_sizes, split_half=True):
            super().__init__()
            self.num_layers = len(cross_layer_sizes)
            self.split_half = split_half
            self.conv_layers = torch.nn.ModuleList()
            prev_dim, fc_input_dim = input_dim, 0
            for i in range(self.num_layers):
                cross_layer_size = cross_layer_sizes[i]
                self.conv_layers.append(torch.nn.Conv1d(input_dim * prev_dim, cross_layer_size, 1,
                                                        stride=1, dilation=1, bias=True))
                if self.split_half and i != self.num_layers - 1:
                    cross_layer_size //= 2
                prev_dim = cross_layer_size
                fc_input_dim += prev_dim
            self.fc = torch.nn.Linear(fc_input_dim, 1)
    
        def forward(self, x):
            """
            :param x: Float tensor of size ``(batch_size, num_fields, embed_dim)``
            """
            xs = list()
            x0, h = x.unsqueeze(2), x
            for i in range(self.num_layers):
                x = x0 * h.unsqueeze(1)
                batch_size, f0_dim, fin_dim, embed_dim = x.shape
                x = x.view(batch_size, f0_dim * fin_dim, embed_dim)
                x = F.relu(self.conv_layers[i](x))
                if self.split_half and i != self.num_layers - 1:
                    x, h = torch.split(x, x.shape[1] // 2, dim=1)
                else:
                    h = x
                xs.append(x)
            return self.fc(torch.sum(torch.cat(xs, dim=1), 2))
    
    
    class AnovaKernel(torch.nn.Module):
    
        def __init__(self, order, reduce_sum=True):
            super().__init__()
            self.order = order
            self.reduce_sum = reduce_sum
    
        def forward(self, x):
            """
            :param x: Float tensor of size ``(batch_size, num_fields, embed_dim)``
            """
            batch_size, num_fields, embed_dim = x.shape
            a_prev = torch.ones((batch_size, num_fields + 1, embed_dim), dtype=torch.float).to(x.device)
            for t in range(self.order):
                a = torch.zeros((batch_size, num_fields + 1, embed_dim), dtype=torch.float).to(x.device)
                a[:, t+1:, :] += x[:, t:, :] * a_prev[:, t:-1, :]
                a = torch.cumsum(a, dim=1)
                a_prev = a
            if self.reduce_sum:
                return torch.sum(a[:, -1, :], dim=-1, keepdim=True)
            else:
                return a[:, -1, :]

     

  • 相关阅读:
    防删没什么意思啊,直接写废你~
    绝大多数情况下,没有解决不了的问题,只有因为平时缺少练习而惧怕问题的复杂度,畏惧的心理让我们选择避让,采取并不那么好的方案去解决问题
    Java 模拟面试题
    Crossthread operation not valid: Control 'progressBar1' accessed from a thread other than the thread it was created on
    一步步从数据库备份恢复SharePoint Portal Server 2003
    【转】理解 JavaScript 闭包
    Just For Fun
    The database schema is too old to perform this operation in this SharePoint cluster. Please upgrade the database and...
    Hello World!
    使用filter筛选刚体碰撞
  • 原文地址:https://www.cnblogs.com/zhangxianrong/p/15152054.html
Copyright © 2020-2023  润新知