2020-01-26 19:09:18 sdu_hao 阅读数 167
  • U-Net图像语义分割实战:训练自己的数据集

    U-Net是一种基于深度学习的图像语义分割方法,尤其在医学图像分割中表现优异。 本课程将手把手地教大家使用labelme图像标注工具制作自己的数据集,生成Mask图像,并使用U-Net训练自己的数据集,从而能开展自己的图像分割应用。 本课程有三个项目实践: (1) Kaggle盐体识别比赛 :利用U-Net进行Kaggle盐体识别 (2) Pothole语义分割:对汽车行驶场景中的路坑进行标注和语义分割 (3) Kaggle细胞核分割比赛 :利用U-Net进行Kaggle细胞核分割 本课程使用keras版本的U-Net,在Ubuntu系统上用Jupyter Notebook做项目演示。 包括:数据集标注、数据集格式转换和Mask图像生成、编写U-Net程序文件、训练自己的数据集、测试训练出的网络模型、性能评估。 本课程提供项目的数据集和Python程序文件。

    864 人正在学习 去看看 白勇

 

接下里我们使用一些深度学习模型,深度学习模型会把机器学习模型的两个阶段联合起来进行end2end学习,即把特征表示和分类一起训练,最后一层进行分类,其余层对输入文本进行特征表示。

我们主要使用了一些经典的深度学习模型,包括FastText、TextCNN、biLSTM、TextGRUCNN等模型,为了增加模型的多样性,每个模型提供了word-level和character-level两个版本(一般来说,word-level效果会更好)。由于数据集做了脱敏处理,没有明文,文本用数字字符串进行了编码,所以暂时不能使用预训练语言模型(如 Bert、RoBerta、ALBert、XLNet等,之后我会还整理一个专栏专门介绍预训练语言模型以及在文本分类上的应用)。

目录

1. 数据预处理

2. FastText

3. TextCNN

4. BiLSTM

5. TextGRU

6. TextGRUCNN

7. 模型训练


1. 数据预处理

  • word-level
if __name__ == '__main__':
    #读取训练集和测试集
    train_df = pd.read_csv('../../input/train_set.csv')
    test_df = pd.read_csv('../../input/test_set.csv')
    
    #训练集
    train_char = train_df['article'].values.tolist() #以字为间隔
    train_word = train_df['word_seg'].values.tolist() #以词为间隔
    train_label = train_df['class'].values - 1 #类别标签 从0开始
    #测试集
    test_char = test_df['article'].values.tolist()
    test_word = test_df['word_seg'].values.tolist()

    np.save('../../data/label', train_label) 

    # np.percentile(train_word_len, [0, 50, 80, 90, 95, 98, 100])
    # of labels: 19
    # Training set
    # [6.     514.     990.    1428.    1949.    2858.48 39759.]
    # [50.     842.    1618.    2346.    3201.    4720.96 55804.]
    # Test set
    # [6.   516.   992.  1429.  1949.  2826. 19755.]
    # [50.   842.  1621.  2349.  3207.  4672. 31694.]

    MAX_LEN = 2000 #序列最大长度  将每个mini-batch的数据都填充成MAX_LEN 方便并行处理
    EMBED_DIM = 300 #词嵌入维度
    #可以基于训练数据 预先训练好word2vec或GloVe词向量,二者可以单独使用也可以进行拼接 增加多样性
    #由于没有明文 不能使用别人开源的预训练词向量   只能自己训练
    EMBED_PATH = '../../data/word_vector_300d.vec' #预训练词向量路径
    NUM_WORDS = 359279 #词典大小

    tokenizer = Tokenizer(num_words=NUM_WORDS) #使用Keras内置的构建词典的函数 默认用空格分词 (官方已经帮我们分好词 并且用空格连接起来了,不然需要手动先分词)
    tokenizer.fit_on_texts(train_word + test_word) #基于训练集和测试集构建词典(一般只基于训练集构建词典)


    #把所有序列/文本中的单词 转换为词典中的索引,并填充为同一长度
    train_sequence = pad_sequences(tokenizer.texts_to_sequences(train_word), MAX_LEN)
    test_sequence = pad_sequences(tokenizer.texts_to_sequences(test_word), MAX_LEN)

    word2vec_mapping = {} #词到向量的映射字典
    with open(EMBED_PATH, 'r') as f: #读取预训练词向量
        lines = f.readlines()[1:]
        for line in lines:
            line = line.strip()
            vals = line.split()
            word2vec_mapping[vals[0]] = np.fromiter(vals[1:], dtype=np.float32)
    print(f'# of words: {len(word2vec_mapping)}')

    oov = 0     #没有预训练词向量的词的个数

    embed_mat = np.random.uniform(-0.1, 0.1, (NUM_WORDS + 1, EMBED_DIM)) #初始化词嵌入矩阵 预留出一个填充词PAD
    for word, i in tokenizer.word_index.items(): 
        if i > NUM_WORDS:
            break
        else:
            if word in word2vec_mapping: #使用预训练词向量覆盖
                embed_mat[i, :] = word2vec_mapping[word]
            else:
                print(i)
                oov += 1
    print(f'# of OOV words: {oov}') #不存在预训练词向量的单词数
    
    # 保存处理好的训练和测试序列
    np.save('../../data/train_input', train_sequence)
    np.save('../../data/test_input', test_sequence)
    # 保存初始化的词嵌入矩阵
    np.save('../../data/word_embed_mat', embed_mat)
  • character-level


if __name__ == '__main__':
    #读取原始数据集
    train_df = pd.read_csv('../../input/train_set.csv')
    test_df = pd.read_csv('../../input/test_set.csv')

    train_char = train_df['article'].values.tolist() #以字为间隔
    train_word = train_df['word_seg'].values.tolist() #以词为间隔
    train_label = train_df['class'].values - 1 #类别标签
    test_char = test_df['article'].values.tolist()
    test_word = test_df['word_seg'].values.tolist()

    np.save('../../data/label', train_label)

    # np.percentile(train_word_len, [0, 50, 80, 90, 95, 98, 100])
    # of labels: 19
    # Training set
    # [6.     514.     990.    1428.    1949.    2858.48 39759.]
    # [50.     842.    1618.    2346.    3201.    4720.96 55804.]
    # Test set
    # [6.   516.   992.  1429.  1949.  2826. 19755.]
    # [50.   842.  1621.  2349.  3207.  4672. 31694.]

    MAX_LEN = 3200  #序列最大长度  将每个mini-batch的数据都填充成MAX_LEN 方便并行处理 character-level序列会更长
    EMBED_DIM = 300 #字潜入维度
    #可以基于训练数据 预先训练好word2vec或GloVe字向量,二者可以单独使用也可以进行拼接 增加多样性
    #由于没有明文 不能使用别人开源的预训练字向量   只能自己训练
    EMBED_PATH = '../../data/char_vector_300d_new.vec' #加载预训练字向量

    tokenizer = Tokenizer()#使用Keras内置的构建字典的函数 默认用空格分字 (官方已经帮我们分好字 并且用空格连接起来了,不然需要手动先分字)
    tokenizer.fit_on_texts(train_char + test_char)#基于训练集和测试集构建字典(一般只基于训练集构建字典)

    
    NUM_WORDS = len([w for w, c in tokenizer.word_counts.items() if c >= 5])  #得到每个字及其频数 过滤掉频数<5的字
    print(NUM_WORDS)    #统计过滤后字的个数,作为字典的大小                 
    
    #可以像之前那样根据一些预先的统计信息 直接指定词典的大小(取词频排在前词典大小的词)   也可以先过滤掉低频词,把剩余的词数作为词典大小
    tokenizer = Tokenizer(num_words=NUM_WORDS)  #构建字典
    tokenizer.fit_on_texts(train_char + test_char)

    #把所有序列/文本中的字 转换为字典中的索引,并填充为同一长度
    train_sequence = pad_sequences(tokenizer.texts_to_sequences(train_char), MAX_LEN)
    test_sequence = pad_sequences(tokenizer.texts_to_sequences(test_char), MAX_LEN)

    char2vec_mapping = {}    #词到向量的映射字典
    with open(EMBED_PATH, 'r') as f:          #读取预训练字向量
        lines = f.readlines()[1:]
        for line in lines:
            line = line.strip()
            vals = line.split()
            char2vec_mapping[vals[0]] = np.fromiter(vals[1:], dtype=np.float32)
    print(f'# of chars: {len(char2vec_mapping)}')

    oov = 0
    embed_mat = np.random.uniform(-0.1, 0.1, (len(char2vec_mapping) + 1, EMBED_DIM)).astype(np.float32)      #初始化字嵌入矩阵 预留出一个填充词PAD
    for char, i in tokenizer.word_index.items():
        if i > NUM_WORDS:
            break
        else:
            if char in char2vec_mapping:
                embed_mat[i, :] = char2vec_mapping[char]    #使用预训练字向量覆盖
            else:
                oov += 1
    print(f'# of OOV words: {oov}')           #不存在预训练字向量的字数
     # 保存处理好的训练和测试序列 character-level
    np.save('../../data/char_train_input', train_sequence)
    np.save('../../data/char_test_input', test_sequence)
    # 保存初始化的字嵌入矩阵
    np.save('../../data/char_embed_mat', embed_mat)

 

2. FastText

  • FastText word-level
class FastText(nn.Module):
    def __init__(self, fc_dim1, fc_dim2, granularity='word'):
        super(FastText, self).__init__()
        # 加载初始化的词嵌入矩阵
        embed_mat = torch.from_numpy(np.load(f'../../data/{granularity}_embed_mat.npy').astype(np.float32))
        num_word, embed_dim = embed_mat.size()
        self.embed = nn.Embedding.from_pretrained(embed_mat, False)
        #两个隐层
        self.fc1 = nn.Linear(embed_dim, fc_dim1)
        self.fc2 = nn.Linear(fc_dim1, fc_dim2)
        #输出层
        self.out = nn.Linear(fc_dim2, 19)
        #激活函数
        self.act = nn.RReLU()
        #BN层
        self.bn1 = nn.BatchNorm1d(fc_dim1)
        self.bn2 = nn.BatchNorm1d(fc_dim2)

    def forward(self, input):
        out = self.embed(input) #(batch_size,max_len) -> (batch_size,max_len,embed_size)
        out = torch.mean(out, dim=1) #(batch_size,embed_size)
        #使用Functional中的Dropout  
        #也可以把Dropout声明为层 使用更方便 丢弃率为0.5 
        out = self.bn1(F.dropout(self.act(self.fc1(out)), p=0.5, training=self.training, inplace=True))#(batch_size,fc_dim1)
        out = self.bn2(F.dropout(self.act(self.fc2(out)), p=0.5, training=self.training, inplace=True))#(batch_size,fc_dim2)
        out = self.out(out)#(batch_size,19)
        return out

    def _initialize_weights(self): #自定义参数初始化方式  
        for m in self.modules():
            if isinstance(m, nn.Conv2d): #卷积层 权重初始化方式 
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None: #卷积层偏置参数初始化为0
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm2d): #BN层权重初始化为1 偏置初始化为0
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear): #全连接层 权重用xavier,偏置初始化为0
                nn.init.xavier_uniform_(m.weight)
                nn.init.constant_(m.bias, 0)
  • FastAttentionText word-level

原始的FastText是将序列中,每个词的词向量直接取平均;FastAttentionText通过Attention机制计算序列中每个词对应的权重,对每个词的词向量进行加权求和。再接几个全连接层做分类。

class Fast_Attention_Text(nn.Module):
    def __init__(self, fc_dim1, fc_dim2):
        super(Fast_Attention_Text, self).__init__()
        self.device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
        # 加载初始化的词嵌入矩阵
        embed_mat = torch.from_numpy(np.load(f'../../data/word_embed_mat.npy').astype(np.float32))
        num_word, embed_dim = embed_mat.size()
        self.embed = nn.Embedding.from_pretrained(embed_mat, False) #False表示不冻结 进行微调

        #初始化一参数向量 作为Query
        self.ctx_vec_size = (300,)
        self.ctx_vec = nn.Parameter(torch.randn(self.ctx_vec_size).float().to(self.device))
        #先定义一个全连接层/映射层
        self.proj = nn.Linear(in_features=300, out_features=300)
        #第一个隐层
        self.fc1 = nn.Linear(embed_dim, fc_dim1)
        #第2个隐层
        self.fc2 = nn.Linear(fc_dim1, fc_dim2)
        #输出层
        self.out = nn.Linear(fc_dim2, 19)
        #激活函数
        self.act = nn.RReLU()
        #BN层
        self.bn1 = nn.BatchNorm1d(fc_dim1)
        self.bn2 = nn.BatchNorm1d(fc_dim2)

    def forward(self, input):
        out = self.embed(input) #(batch_size,sentence_len) -> (batch_size,sentence_len,embed_dim)

        u = F.tanh(self.proj(out))  # [batch, sentence_len, embed_dim]每个词转换为词向量后 通过一个映射层 结果作为Key
        #将结果和参数向量计算内积(Query和Key计算内积)  (batch,sentence_len) 沿sentence_len方向计算softmax 计算句子中每个词向量对应的权重
        a = F.softmax(torch.einsum('bse,e->bs', (u.clone(), self.ctx_vec.clone())), dim=1)
        #对Value 即out作加权求和
        s = torch.einsum('bse,bs->be', (out.clone(), a.clone())) #(batch,embed)
        #将结果通过两个隐层
        out = self.bn1(F.dropout(self.act(self.fc1(s)), p=0.5, training=self.training, inplace=True)) #(batch,fc_dim1)
        out = self.bn2(F.dropout(self.act(self.fc2(out)), p=0.5, training=self.training, inplace=True))#(batch,fc_dim2)
        #输出层
        out = self.out(out) ##(batch,19)
        return out

    def _initialize_weights(self):#自定义参数初始化方式 同上
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm1d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_normal_(m.weight)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
  • FastText character-level
class FastText(nn.Module):
    def __init__(self, fc_dim1, fc_dim2):
        super(FastText, self).__init__()
        #加载初始化的字潜入矩阵
        embed_mat = torch.from_numpy(np.load(f'../../data/char_embed_mat.npy'))
        num_word, embed_dim = embed_mat.size()
        self.embed = nn.Embedding.from_pretrained(embed_mat)
        #两个隐层
        self.fc1 = nn.Linear(embed_dim, fc_dim1, bias=False)
        self.fc2 = nn.Linear(fc_dim1, fc_dim2, bias=False)
        #输出层
        self.out = nn.Linear(fc_dim2, 19)
        #激活函数
        self.act = nn.PReLU()
        #BN层
        self.bn1 = nn.BatchNorm1d(fc_dim1)
        self.bn2 = nn.BatchNorm1d(fc_dim2)

    def forward(self, input):
        out = self.embed(input) #(batch_size,max_len) -> (batch_size,max_len,embed_size)

        out = torch.mean(out, dim=1)#(batch_size,embed_size)
        # 使用Functional中的Dropout
        # 也可以把Dropout声明为层 使用更方便 丢弃率为0.5
        out = self.bn1(F.dropout(self.act(self.fc1(out)), p=0.5, training=self.training, inplace=True))#(batch_size,fc_dim1)
        out = self.bn2(F.dropout(self.act(self.fc2(out)), p=0.2, training=self.training, inplace=True))#(batch_size,fc_dim2)
        out = self.out(out)#(batch_size,19)
        return out
 
    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm1d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)

 

3. TextCNN

  • word-level
class TextCNN(nn.Module):
    def __init__(self, filter_sizes, num_filter, fc_dim1):
        super(TextCNN, self).__init__()
        #加载初始化的词潜入矩阵
        embed_mat = torch.from_numpy(np.load('../../data/word_embed_mat.npy').astype(np.float32)) #读取预训练
        num_word, embed_dim = embed_mat.size()
        self.embed = nn.Embedding.from_pretrained(embed_mat, freeze=False)
        
        #用多个不同大小的卷积核 提取特征
        self.conv = nn.ModuleList([nn.Conv2d(1, num_filter, (size, embed_dim), bias=False) for size in filter_sizes])
        #激活函数
        self.act = nn.RReLU()
        #隐层
        self.fc = nn.Linear(len(filter_sizes) * num_filter, fc_dim1)
        #输出层
        self.out = nn.Linear(fc_dim1, 19)
        
        #BN层
        self.bn1 = nn.BatchNorm1d(len(filter_sizes) * num_filter)
        self.bn2 = nn.BatchNorm1d(fc_dim1)
        self._initialize_weights()  #运行自定义初始化方式 否则将采用默认初始化

    def forward(self, input):
        #将输入通过词嵌入矩阵转换为词向量后 在接一个dropout    (batch,sentence_len,embed)
        embed_out = F.dropout(self.embed(input), p=0.1, training=self.training, inplace=True)
        embed_out = embed_out.unsqueeze(1) #添加通道维 (batch,1, sentence_len,embed) 进行2维卷积
        conv_out = [self.act(conv(embed_out)).squeeze(3) for conv in self.conv]
        conv_out = [F.max_pool1d(i, i.size(2)).squeeze(2) for i in conv_out] #全局最大池化
        conv_out = torch.cat(conv_out, dim=1) #将池化结果进行拼接
        #通过dropout 和 BN层
        out = self.bn1(F.dropout(conv_out, p=0.5, training=self.training, inplace=True))
        fc_out = self.bn2(F.dropout(self.act(self.fc(out)), p=0.25, training=self.training, inplace=True))
        out = self.out(fc_out) #输出层(batch,19)
        return out

    def _initialize_weights(self): #自定义初始化方式
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm1d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_normal_(m.weight)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
  • TextCNN character-level

代码和word-level相同,只是把词向量换为字向量,输入是基于字分隔的,不是基于词。

class TextCNN(nn.Module):
    def __init__(self, filter_sizes, num_filter, fc_dim):
        super(TextCNN, self).__init__()
        embed_mat = torch.from_numpy(np.load('../../data/char_embed_mat.npy').astype(np.float32))
        num_word, embed_dim = embed_mat.size()
        self.embed = nn.Embedding.from_pretrained(embed_mat)
        self.conv = nn.ModuleList([nn.Conv2d(1, num_filter, (size, embed_dim)) for size in filter_sizes])
        self.act = nn.PReLU()
        self.fc = nn.Linear(len(filter_sizes) * num_filter, fc_dim, bias=False)
        self.out = nn.Linear(fc_dim, 19)
        self.bn1 = nn.BatchNorm1d(len(filter_sizes) * num_filter)
        self.bn2 = nn.BatchNorm1d(fc_dim)
        self._initialize_weights()

    def forward(self, input):
        embed_out = F.dropout(self.embed(input), p=0.2, training=self.training, inplace=True)
        embed_out = embed_out.unsqueeze(1)
        conv_out = [self.act(conv(embed_out)).squeeze(3) for conv in self.conv]
        conv_out = [F.max_pool1d(i, i.size(2)).squeeze(2) for i in conv_out]
        conv_out = torch.cat(conv_out, dim=1)
        out = self.bn1(F.dropout(conv_out, p=0.5, training=self.training, inplace=True))
        fc_out = self.bn2(F.dropout(self.act(self.fc(out)), p=0.2, training=self.training, inplace=True))
        out = self.out(fc_out)
        return out

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm1d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_normal_(m.weight)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)

 

4. BiLSTM

  • 单层BiLSTM word-level

对单层BiLSTM的输出结果,沿长度方向作最大池化和平均池化,把两个池化结果拼接,再接几个全连接层进行分类。

class Pooled_BiLSTM(nn.Module):
    def __init__(self, hidden_dim, fc_dim):
        super(Pooled_BiLSTM, self).__init__()
        #加载初始化词嵌入矩阵
        embed_mat = torch.from_numpy(np.load('../../data/word_embed_mat.npy').astype(np.float32))
        embed_dim = embed_mat.size(1)
        self.device = 'cuda:0' if torch.cuda.is_available() else 'cpu' #可以在训练之前设置 直接model.to(device) 没必要在类内部设置
        self.embed = nn.Embedding.from_pretrained(embed_mat)
        
        #单层双向LSTM batch_size为第一个维度
        self.lstm = nn.LSTM(input_size=embed_dim, hidden_size=hidden_dim, batch_first=True, bidirectional=True)
        #隐层和输出层
        self.fc = nn.Linear(4 * hidden_dim, fc_dim)
        self.out = nn.Linear(fc_dim, 19)
        #BN层
        self.bn1 = nn.BatchNorm1d(4 * hidden_dim)
        self.bn2 = nn.BatchNorm1d(fc_dim)

        self._initialize_weights()

    def forward(self, input):
        #将输入通过词嵌入矩阵得到词向量 在通过dropout 丢弃率为0.2 
        out = F.dropout(self.embed(input), p=0.2, training=self.training, inplace=True)
        out, _ = self.lstm(out) #得到lstm输出 (batch,sentence_len,hidden*2)
        max_pool, _ = torch.max(out, dim=1) #最大池化 (batch,hidden*2)
        avg_pool = torch.mean(out, dim=1)#平均池化 (batch,hidden*2)
        #将池化结果拼接 再通过dropout 丢弃率为0.3 再通过BN (batch,hidden*4)
        out = self.bn1(F.dropout(torch.cat((max_pool, avg_pool), dim=1), p=0.3, training=self.training, inplace=True))
        #再通过隐层 dropout 和BN (batch,fc_dim)
        out = self.bn2(F.dropout(self.fc(out), p=0.5, training=self.training, inplace=True))
        #输出层 
        out = self.out(out) #(batch,19)
        return out

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.LSTM):
                for name, param in m.named_parameters():
                    if 'weight' in name:
                        nn.init.orthogonal_(param)
                    elif 'bias' in name:
                        nn.init.constant_(param, 0.0)
  • 2层BiLSTM word-level

代码与单层BiLSTM word-level相同,只不过lstm变成了两层。

class Pooled_BiLSTM(nn.Module):
    def __init__(self, hidden_dim, fc_dim):
        super(Pooled_BiLSTM, self).__init__()
        embed_mat = torch.from_numpy(np.load('../../data/word_embed_mat.npy').astype(np.float32))
        embed_dim = embed_mat.size(1)
        self.device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
        self.embed = nn.Embedding.from_pretrained(embed_mat)
        self.lstm = nn.LSTM(input_size=embed_dim, hidden_size=hidden_dim, batch_first=True, bidirectional=True, num_layers=2)
        self.fc = nn.Linear(4 * hidden_dim, fc_dim)
        self.out = nn.Linear(fc_dim, 19)
        self.bn1 = nn.BatchNorm1d(4 * hidden_dim)
        self.bn2 = nn.BatchNorm1d(fc_dim)

        self._initialize_weights()

    def forward(self, input):
        out = F.dropout(self.embed(input), p=0.2, training=self.training, inplace=True)
        out, _ = self.lstm(out)
        max_pool, _ = torch.max(out, dim=1)
        avg_pool = torch.mean(out, dim=1)
        out = self.bn1(F.dropout(torch.cat((max_pool, avg_pool), dim=1), p=0.3, training=self.training, inplace=True))
        out = self.bn2(F.dropout(self.fc(out), p=0.5, training=self.training, inplace=True))
        out = self.out(out)
        return out

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.LSTM):
                for name, param in m.named_parameters():
                    if 'weight' in name:
                        nn.init.orthogonal_(param)
                    elif 'bias' in name:
                        nn.init.constant_(param, 0.0)

 
  • 2层BiLSTM word-level(High_Dropout)

代码与单层BiLSTM word-level相同,只不过lstm变成了两层,将两种池化结果拼接后直接跟输出层进行分类,中间没有加隐层和BN层,速度更快。


class High_Dropout_Pooled_BiLSTM(nn.Module):
    def __init__(self, hidden_dim):
        super(High_Dropout_Pooled_BiLSTM, self).__init__()
        embed_mat = torch.from_numpy(np.load('../../data/word_embed_mat.npy').astype(np.float32))
        embed_dim = embed_mat.size(1)
        self.device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
        self.embed = nn.Embedding.from_pretrained(embed_mat)
        self.lstm = nn.LSTM(input_size=embed_dim, hidden_size=hidden_dim, batch_first=True, bidirectional=True,
                            num_layers=2)
        self.out = nn.Linear(4 * hidden_dim, 19)

        self._initialize_weights()

    def forward(self, input):
        out = F.dropout(self.embed(input), p=0.5, training=self.training, inplace=True)
        out, _ = self.lstm(out)
        max_pool, _ = torch.max(out, dim=1)
        avg_pool = torch.mean(out, dim=1)
        out = torch.cat((max_pool, avg_pool), dim=1)
        out = self.out(out)
        return out

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm1d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_normal_(m.weight)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.LSTM):
                for name, param in m.named_parameters():
                    if 'weight' in name:
                        nn.init.orthogonal_(param)
                    elif 'bias' in name:
                        nn.init.constant_(param, 0.0)

5. TextGRU

word-level,将2层双向GRU的输出,沿长度方向作最大池化、平均池化,取最后时刻的隐藏状态再通过Attention机制计算得到一个隐状态(各时刻隐状态的加权和),将四者进行拼接,再通过隐层、输出层进行分类。

class TextGRU_Ultimate(nn.Module):
    def __init__(self, hidden_dim, fc_dim):
        super(TextGRU_Ultimate, self).__init__()
        #加载初始化的词嵌入矩阵
        embed_mat = torch.from_numpy(np.load('../../data/word_embed_mat.npy').astype(np.float32))
        embed_dim = embed_mat.size(1)
        self.hidden_dim = hidden_dim
        self.device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
        self.embed = nn.Embedding.from_pretrained(embed_mat)
        
        #2层双向GRU batch_size为第一个维度
        self.lstm = nn.GRU(input_size=embed_dim, hidden_size=hidden_dim, batch_first=True, bidirectional=True,
                           num_layers=2)
        
        self.ctx_vec_size = (2 * hidden_dim,) #初始化一个参数向量 作为query
        self.ctx_vec = nn.Parameter(torch.randn(self.ctx_vec_size).float().to(self.device))
        self.proj = nn.Linear(in_features=2 * hidden_dim, out_features=2 * hidden_dim)
        self.fc = nn.Linear(8 * hidden_dim, fc_dim, bias=False)
        self.act = nn.RReLU()
        self.out = nn.Linear(fc_dim, 19)
        self.bn1 = nn.BatchNorm1d(8 * hidden_dim)
        self.bn2 = nn.BatchNorm1d(fc_dim)

        self._initialize_weights()

    def forward(self, input):
        # input_shape = (input.size(0), input.size(1))
        # probs = torch.empty(input_shape).uniform_(0, 1).to(self.device)
        # spatial_dropout_input = torch.where(probs > 0.2, input,
        #                                     torch.zeros(input_shape, dtype=torch.int64).to(self.device))
        # del probs
        
        #将输入通过词嵌入矩阵后转为词向量 在通过dropout 丢弃率为0.2
        out = F.dropout(self.embed(input), p=0.2, training=self.training, inplace=True)
        out, _ = self.lstm(out) #(batch,sentence_len,hidden*2) gru的结果 作为value

        # last_pool = torch.cat((out[:, -1, :self.hidden_dim], out[:, 0, self.hidden_dim:]), dim=1)
        last_pool = out[:, -1, :] #最后时刻的隐状态
        max_pool, _ = torch.max(out, dim=1) #最大池化 (batch,hidden*2)
        avg_pool = torch.mean(out, dim=1) #平均池化 (batch,hidden*2)
        u = F.tanh(self.proj(out))  # [batch, sentence_len, 2*hidden_dim] gru输出通过映射层 将结果作为key
        a = F.softmax(torch.einsum('bsh,h->bs', (u.clone(), self.ctx_vec.clone())), dim=1) #key和query作点积 结果通过softmax转换为权重
        attention_pool = torch.einsum('bsh,bs->bh', (out.clone(), a.clone())) #将权重和value加权求和 即对每一时刻的隐状态加权求和

        pool = torch.cat((last_pool, max_pool, avg_pool, attention_pool), dim=1) #把最后时刻的隐状态 各个时刻隐状态的最大池化、平均池化以及attention计算的结果作拼接 (batch,hidden*8)
        #将拼接结果通过 dropout和bn
        out = self.bn1(F.dropout(pool, p=0.5, training=self.training, inplace=True))
        #再通过隐层 激活函数 dropout bn (batch,fc_dim)
        out = self.bn2(F.dropout(self.act(self.fc(out)), p=0.2, training=self.training, inplace=True))
        #输出层 (batch,19)
        out = self.out(out)
        return out

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm1d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_normal_(m.weight)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.GRU):
                for name, param in m.named_parameters():
                    if 'weight' in name:
                        nn.init.orthogonal_(param)
                    elif 'bias' in name:
                        nn.init.constant_(param, 0.0)

6. TextGRUCNN

word-level,将单层双向GRU的输出结果,再通过TextCNN用不同大小的卷积核提取特征,再作全局最大池化,对池化结果进行拼接,再接隐层和全连接层进行分类。

class TextGRUCNN(nn.Module):
    def __init__(self, hidden_dim, num_filter, fc_dim):
        super(TextGRUCNN, self).__init__()
        #加载初始化的词嵌入矩阵
        embed_mat = torch.from_numpy(np.load('../../data/word_embed_mat.npy').astype(np.float32))
        embed_dim = embed_mat.size(1)
        self.device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
        self.embed = nn.Embedding.from_pretrained(embed_mat)
        #单层双向GRU batch_size是第一维度
        self.gru = nn.GRU(input_size=embed_dim, hidden_size=hidden_dim, batch_first=True, bidirectional=True)
        #声明5种不同大小的2维卷积核
        self.conv = nn.ModuleList(
            [nn.Conv2d(1, num_filter, (size, 2 * hidden_dim), bias=False) for size in range(1, 6)])
        self.act = nn.PReLU()
        self.fc = nn.Linear(5 * num_filter, fc_dim, bias=False)
        self.out = nn.Linear(fc_dim, 19)
        self.bn1 = nn.BatchNorm1d(5 * num_filter)
        self.bn2 = nn.BatchNorm1d(fc_dim)

        self._initialize_weights()

    def forward(self, input):
        #将输入通过词嵌入矩阵转为词向量 在通过dropout 丢弃率为0.2
        out = F.dropout(self.embed(input), p=0.2, training=self.training, inplace=True)
        out, _ = self.gru(out)  # [batch, sentence_len, 2*hidden_dim]
        out = out.unsqueeze(dim=1) #增加一个通道维 (batch,1,sentence_len,2*hidden_dim) 方便进行卷积运算
        out = [self.act(conv(out)).squeeze(3) for conv in self.conv] #对GRU的输出 通过多个不同大小的卷积核 提取特征
        out = [F.max_pool1d(i, i.size(2)).squeeze(2) for i in out] #对卷积结果作全局最大池化
        out = torch.cat(out, dim=1) #将池化结果拼接在一起 (batch,5*num_filter)
        #通过dropout 和 bn
        out = self.bn1(F.dropout(out, p=0.5, training=self.training, inplace=True))
        #再通过隐层 激活函数 dropout bn (batch,fc_dim)
        out = self.bn2(F.dropout(self.act(self.fc(out)), p=0.2, training=self.training, inplace=True))
        #输出层
        out = self.out(out)#(batch,19)
        return out

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm1d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_normal_(m.weight)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.GRU):
                for name, param in m.named_parameters():
                    if 'weight' in name:
                        nn.init.orthogonal_(param)
                    elif 'bias' in name:
                        nn.init.constant_(param, 0.0)

7. 模型训练

  • hold_out

将训练集留出一小部分作验证集,其余全部进行训练:

def hold_out_test(model_fn, model_name, train_data, train_label, test_data, batch_size=128, lr=1e-3,
                  num_folds=10, seed=1):

    #将训练集分为 训练集和验证集
    device = 'cuda:0' if torch.cuda.is_available() else 'cpu:0'

    #设置随机种子
    np.random.seed(seed)
    if torch.cuda.is_available():
        torch.cuda.manual_seed(seed)
    else:
        torch.manual_seed(seed)

    #num_folds折交叉验证  10折
    skf = StratifiedKFold(n_splits=num_folds, shuffle=True, random_state=2018)
    skf_indices = []
    for i, (train_idx, valid_idx) in enumerate(skf.split(np.zeros(len(train_label)), train_label)):
        skf_indices.extend(valid_idx)

    fold_len = len(train_label) // num_folds #每一折的数据量

    fold = 2 #确定第几折为测试集
    print(f'Processing fold {fold}...')
    fold_start = fold * fold_len
    fold_end = (fold + 1) * fold_len

    if fold == 0:
        #训练集为 1-8折
        train_indices = skf_indices[fold_len:(num_folds - 1) * fold_len]
        #验证集为最后一折
        valid_indices = skf_indices[(num_folds - 1) * fold_len:]
    elif fold == num_folds - 1:
        #训练集为 0-7折
        train_indices = skf_indices[:((num_folds - 2) * fold_len)]
        #验证集为 8折 倒数第2折
        valid_indices = skf_indices[(num_folds - 2) * fold_len:(num_folds - 1) * fold_len]
        fold_end = len(train_label)
    else:
        #验证集为交叉测试集的前一折
        valid_indices = skf_indices[fold_start - fold_len:fold_start]
        #训练集为剩下的
        train_indices = skf_indices[:fold_start - fold_len] + skf_indices[fold_end:]
    test_indices = skf_indices[fold_start:fold_end]

    #获取索引对应的数据及标签
    train_x, valid_x, test_x = train_data[train_indices], train_data[valid_indices], train_data[test_indices]
    train_y, valid_y, test_y = train_label[train_indices], train_label[valid_indices], train_label[test_indices]

    train_size = len(train_y)
    valid_size = len(valid_y)
    print(f'# of training samples: {train_size}')
    print(f'# of validation samples: {valid_size}')

    #转换为tensor 并to(device) 这样训练时就可以不to(device)了
    train_x_tensor = torch.from_numpy(train_x).long().to(device)
    train_y_tensor = torch.from_numpy(train_y).long().to(device)
    # train_y_tensor = torch.from_numpy(train_y).float().to(device)
    valid_x_tensor = torch.from_numpy(valid_x).long().to(device)
    valid_y_tensor = torch.from_numpy(valid_y).long().to(device)
    # valid_y_tensor = torch.from_numpy(valid_y).float().to(device)
    test_x_tensor = torch.from_numpy(test_x).long().to(device)
    test_y_tensor = torch.from_numpy(test_y).long().to(device)

    #构建Dataset
    train_dataset = TensorDataset(train_x_tensor, train_y_tensor)
    valid_dataset = TensorDataset(valid_x_tensor, valid_y_tensor)
    test_dataset = TensorDataset(test_x_tensor, test_y_tensor)

    #构建DataLoader
    train_loader = DataLoader(train_dataset, batch_size, True) #True表示打乱
    valid_loader = DataLoader(valid_dataset, batch_size)

    model = model_fn().to(device) #把模型整体to(device)

    criterion = nn.CrossEntropyLoss(size_average=False) #多分类交叉熵损失函数 不按样本取平均
    #找出需要训练的参数
    trainable_params = [p for p in model.parameters() if p.requires_grad]
    #对需要训练的参数定义优化器 学习率
    optimizer = Adam(trainable_params, lr)
    #如果一轮epoch训练结束 相较上轮验证集上的f1-score没有上升, 学习率减半。也可以把忍耐值patience设置的大一些 学习率最小减到min_lr为止
    scheduler = ReduceLROnPlateau(optimizer, 'max', factor=0.5, patience=1, min_lr=5e-6, verbose=True)

    epochs = 1000 #完整遍历训练集的次数
    patience = 5
    best_epoch = -1
    best_f1 = -1

    for epoch in range(epochs):

        # Early Stopping
        if epoch - best_epoch > patience:
            print(f'No improvement for {patience} epochs, stop training...')
            break

        start = time.time()

        # Training phase
        model.train()
        train_loss = 0.0 #每一轮epoch训练集的平均损失
        y_true, y_pred = [], [] #存储每一轮 样本的真实标签和预测标签
        for data in train_loader:
            input, y = data
            optimizer.zero_grad()
            outputs = model(input).squeeze()

            loss = criterion(outputs, y)
            y_true.append(y)
            y_pred.append(outputs.cpu().detach().numpy().argmax(axis=1))
            loss.backward() #计算梯度
            clip_grad_norm_(trainable_params, 1.0) #梯度剪切
            optimizer.step() #更新参数
            train_loss += loss.item() / train_size
            del input, y
        y_true = np.concatenate(y_true)
        y_pred = np.concatenate(y_pred)
        #计算macro f1-score
        train_f1 = f1_score(y_true, y_pred, average='macro')
        del y_pred, y_true

        # Validation phase 每一轮epoch训练结束,在验证集上做一次验证
        model.eval()
        valid_loss = 0.0#每一轮epoch验证集的平均损失
        pred = []
        with torch.no_grad(): 
            for data in valid_loader:
                input, y = data
                outputs = model(input).squeeze()
                loss = criterion(outputs, y)
                pred.append(outputs.cpu().detach().numpy().argmax(axis=1))
                valid_loss += loss.item() / valid_size
                del input, y
        valid_y_pred = np.concatenate(pred)
        valid_f1 = f1_score(valid_y, valid_y_pred, average='macro')
        scheduler.step(valid_f1)
        del valid_y_pred

        if valid_f1 > best_f1:
            best_epoch = epoch
            best_f1 = valid_f1
            elapsed = time.time() - start
            print(f'Epoch {epoch} in {elapsed:.1f}s: improved!')
            print(f'    train loss: {train_loss:.4f} valid loss: {valid_loss:.4f} ')
            print(f'    train f1_macro: {train_f1:.4f} valid f1_macro: {valid_f1:.4f}')
        else:
            elapsed = time.time() - start
            print(f'Epoch {epoch} in {elapsed:.1f}s:')
            print(f'    train loss: {train_loss:.4f} valid loss: {valid_loss:.4f}')
            print(f'    train f1_macro: {train_f1:.4f} valid f1_macro: {valid_f1:.4f}')
  • cross validation

由于深度学习的数据量巨大,我们一般不采用交叉验证,但是为了尽可能提高比赛结果,可以尝试使用,不过需要很大的计算代价。

def cross_validation_bagging(model_fn, model_name, train_data, train_label, test_data, batch_size=128, lr=1e-3,
                             num_folds=10, patience=10, seed=1):
    device = 'cuda:0' if torch.cuda.is_available() else 'cpu:0'

    np.random.seed(seed)
    if torch.cuda.is_available():
        torch.cuda.manual_seed(seed)
    else:
        torch.manual_seed(seed)

    #10折交叉验证
    skf = StratifiedKFold(n_splits=num_folds, shuffle=True, random_state=2018)
    skf_indices = []
    for i, (train_idx, valid_idx) in enumerate(skf.split(np.zeros(len(train_label)), train_label)):
        skf_indices.extend(valid_idx)

    fold_len = len(train_label) // num_folds #每一折的数据量
    num_classes = len(set(train_label)) #类别数
    meta_train = np.zeros((len(train_label), num_classes)) #在训练集上的预测结果
    meta_test = np.zeros((test_data.shape[0], num_classes)) #在测试集上的预测结果

    #将测试数据转换为tensor to(device)
    test_data_tensor = torch.from_numpy(test_data).long().to(device)
    test_label_tensor = torch.zeros(len(test_data)).long().to(device)
    #对测试数据构建Dataset 和 DataLoader
    test_data_dataset = TensorDataset(test_data_tensor, test_label_tensor)
    test_data_loader = DataLoader(test_data_dataset, batch_size)

    print(f'test_data_dataset & test_data_loader: {torch.cuda.memory_allocated() / 1024 ** 3:.2f}GB')

    for fold in range(num_folds):
        print(f'Loop Begin: {torch.cuda.memory_allocated() / 1024 ** 3:.2f}GB')
        print(f'Processing fold {fold}...')
        fold_start = fold * fold_len
        fold_end = (fold + 1) * fold_len

        #将训练集划分为训练集、验证集、交叉测试集 fold决定哪个是交叉测试集
        if fold == 0:
            train_indices = skf_indices[fold_len:(num_folds - 1) * fold_len]
            valid_indices = skf_indices[(num_folds - 1) * fold_len:]
        elif fold == num_folds - 1:
            train_indices = skf_indices[:((num_folds - 2) * fold_len)]
            valid_indices = skf_indices[(num_folds - 2) * fold_len:(num_folds - 1) * fold_len]
            fold_end = len(train_label)
        else:
            valid_indices = skf_indices[fold_start - fold_len:fold_start]
            train_indices = skf_indices[:fold_start - fold_len] + skf_indices[fold_end:]
        test_indices = skf_indices[fold_start:fold_end]
        train_x, valid_x, test_x = train_data[train_indices], train_data[valid_indices], train_data[test_indices]
        train_y, valid_y, test_y = train_label[train_indices], train_label[valid_indices], train_label[test_indices]

        train_size = len(train_y)
        valid_size = len(valid_y)
        print(f'# of training samples: {train_size}')
        print(f'# of validation samples: {valid_size}')

        train_x_tensor = torch.from_numpy(train_x).long().to(device)
        train_y_tensor = torch.from_numpy(train_y).long().to(device)
        # train_y_tensor = torch.from_numpy(train_y).float().to(device)
        valid_x_tensor = torch.from_numpy(valid_x).long().to(device)
        valid_y_tensor = torch.from_numpy(valid_y).long().to(device)
        # valid_y_tensor = torch.from_numpy(valid_y).float().to(device)
        test_x_tensor = torch.from_numpy(test_x).long().to(device)
        test_y_tensor = torch.from_numpy(test_y).long().to(device)

        train_dataset = TensorDataset(train_x_tensor, train_y_tensor)
        valid_dataset = TensorDataset(valid_x_tensor, valid_y_tensor)
        test_dataset = TensorDataset(test_x_tensor, test_y_tensor)

        train_loader = DataLoader(train_dataset, batch_size, True)
        valid_loader = DataLoader(valid_dataset, batch_size)
        test_loader = DataLoader(test_dataset, batch_size)

        model = model_fn().to(device)

        criterion = nn.CrossEntropyLoss(size_average=False)
        trainable_params = [p for p in model.parameters() if p.requires_grad]
        optimizer = Adam(trainable_params, lr)
        scheduler = ReduceLROnPlateau(optimizer, 'max', factor=0.5, patience=1, min_lr=5e-6, verbose=True)

        epochs = 1000
        best_epoch = -1
        best_f1 = -1
        best_loss = 1e3
        ckpt_path = None

        for epoch in range(epochs):

            # Early Stopping
            if epoch - best_epoch > patience:
                print(f'No improvement for {patience} epochs, stop training...')
                break

            start = time.time()

            # Training phase
            model.train()
            train_loss = 0.0
            y_true, y_pred = [], []
            for data in train_loader:
                input, y = data
                optimizer.zero_grad()
                outputs = model(input).squeeze()
                loss = criterion(outputs, y)
                y_true.append(y)
                y_pred.append(outputs.cpu().detach().numpy().argmax(axis=1))
                loss.backward()
                clip_grad_norm_(trainable_params, 1.0)
                optimizer.step()
                train_loss += loss.item() / train_size
                del input, y
            y_true = np.concatenate(y_true)
            y_pred = np.concatenate(y_pred)
            train_f1 = f1_score(y_true, y_pred, average='macro')
            del y_pred, y_true

            # Validation phase 每一个epoch结束在验证集上验证
            model.eval()
            valid_loss = 0.0
            pred = []
            with torch.no_grad():
                for data in valid_loader:
                    input, y = data
                    outputs = model(input).squeeze()
                    loss = criterion(outputs, y)
                    pred.append(outputs.cpu().detach().numpy().argmax(axis=1))
                    valid_loss += loss.item() / valid_size
                    del input, y
            valid_y_pred = np.concatenate(pred)
            valid_f1 = f1_score(valid_y, valid_y_pred, average='macro')
            scheduler.step(valid_f1)
            del valid_y_pred

            if valid_f1 > best_f1: #保存在验证集上 f1-score最高的一组参数
                # Remove stale checkpoint
                if ckpt_path is not None:
                    os.remove(ckpt_path)
                best_epoch = epoch
                best_f1 = valid_f1
                ckpt_path = f'../../ckpt/{model_name}/{seed}/fold{fold}.ckpt'
                if not os.path.exists(os.path.dirname(ckpt_path)):
                    os.makedirs(os.path.dirname(ckpt_path))
                torch.save(model.state_dict(), ckpt_path)
                elapsed = time.time() - start
                print(f'Epoch {epoch} in {elapsed:.1f}s: saved in {ckpt_path}')
                print(f'    train loss: {train_loss:.4f} valid loss: {valid_loss:.4f} ')
                print(f'    train f1_macro: {train_f1:.4f} valid f1_macro: {valid_f1:.4f}')
            else:
                elapsed = time.time() - start
                print(f'Epoch {epoch} in {elapsed:.1f}s: not improved')
                print(f'    train loss: {train_loss:.4f} valid loss: {valid_loss:.4f}')
                print(f'    train f1_macro: {train_f1:.4f} valid f1_macro: {valid_f1:.4f}')

        #一次训练结束后 加载当前最好的参数   (10折 有10次训练)
        model.load_state_dict(torch.load(ckpt_path))

        pred = []
        model.eval()
        with torch.no_grad(): #在交叉测试集上测试
            for data in test_loader:
                input, _ = data
                outputs = model(input).squeeze()
                pred.append(outputs)
                del input
        pred = np.concatenate(pred, axis=0)
        meta_train[test_indices] = softmax(pred) #将预测结果先通过softmax 转换为概率分布 在把值赋给完整训练集的相应的位置 10折训练完后 将会得到完整训练集的预测结果
        del pred

        pred = []
        model.eval()
        with torch.no_grad():    #在测试集上测试
            for data in test_data_loader:
                input, _ = data
                outputs = model(input).squeeze()
                pred.append(outputs)
                del input
        pred = np.concatenate(pred, axis=0)  #得到在测试集上的预测结果
        meta_test += softmax(pred) / num_folds #10个在测绘集上的预测结果 直接取平均
        del pred

        del train_dataset, valid_dataset, test_dataset
        del train_loader, valid_loader, test_loader
        del train_x_tensor, train_y_tensor, valid_x_tensor, valid_y_tensor, test_x_tensor, test_y_tensor
        del model
        del criterion, optimizer, scheduler
        torch.cuda.empty_cache()
        gc.collect()

    oof_pred = np.argmax(meta_train, axis=1) #得到在完整训练集上的预测标签
    oof_f1_macro = f1_score(train_label, oof_pred, average='macro') #计算macro f1-score
    #保存在完整训练集上的预测结果
    meta_train_path = f'../../oof_pred/{model_name}/{seed}/{model_name}_train_{oof_f1_macro:.4f}'
    if not os.path.exists(os.path.dirname(meta_train_path)):
        os.makedirs(os.path.dirname(meta_train_path))
    np.save(meta_train_path, meta_train)
    #保存在测试集上的预测结果
    meta_test_path = f'../../oof_pred/{model_name}/{seed}/{model_name}_test_{oof_f1_macro:.4f}'
    if not os.path.exists(os.path.dirname(meta_test_path)):
        os.makedirs(os.path.dirname(meta_test_path))
    np.save(meta_test_path, meta_test)

 

2019-05-27 14:08:31 Tao_758 阅读数 326
  • U-Net图像语义分割实战:训练自己的数据集

    U-Net是一种基于深度学习的图像语义分割方法,尤其在医学图像分割中表现优异。 本课程将手把手地教大家使用labelme图像标注工具制作自己的数据集,生成Mask图像,并使用U-Net训练自己的数据集,从而能开展自己的图像分割应用。 本课程有三个项目实践: (1) Kaggle盐体识别比赛 :利用U-Net进行Kaggle盐体识别 (2) Pothole语义分割:对汽车行驶场景中的路坑进行标注和语义分割 (3) Kaggle细胞核分割比赛 :利用U-Net进行Kaggle细胞核分割 本课程使用keras版本的U-Net,在Ubuntu系统上用Jupyter Notebook做项目演示。 包括:数据集标注、数据集格式转换和Mask图像生成、编写U-Net程序文件、训练自己的数据集、测试训练出的网络模型、性能评估。 本课程提供项目的数据集和Python程序文件。

    864 人正在学习 去看看 白勇

前言:从小白入门,通过这次比赛学到很多东西。现在把文档放到这里,希望能够帮助到需要的人。 特别感谢初赛排名第一的YaHei大佬。感谢他开源的代码把我带进了比赛的大门。附上大佬的项目链接:

https://github.com/hey-yahei/ZTE_Challenge2019_MOA

摘要: 本次模型压缩几乎无法重新训练,因此无法采用许多常用的模型压缩算法。针对主办方提供的已经训练完成的模型,主要使用了包括层融合、剪枝、奇异值分解等方法进行压缩。

一、 层融合

​   在神经网络训练的过程中,BN层能够加速网络收敛,并且能够控制过拟合。不过这样也增加了一些运算和参数,因此在推理过程中,可以通过将BN层与卷积层的参数融合的方式,来减少运算,并略微地实现一些模型的压缩。【1】

​   公式:

​   卷积层计算公式:
Y=WX+b Y = W*X + b

​   BN层计算公式:
Xbn=s(Xm)(σ+ϵ)+bbn X_{bn} = \frac{s(X - m)}{\sqrt(\sigma + \epsilon)} + b_{bn}
​   其中:m: 均值, $ \sigma$ : 方差, s: scale, $ b_{bn}$: 偏置, ϵ\epsilon : 滑动系数。

​   融合及将卷积层的输出作为BN层的输入,带入得到

​   融合公式:
Xbn=XsWσ+ϵ+s(bm)σ+ϵ+bbn X_{bn} = X * \frac{sW}{\sqrt{\sigma + \epsilon}} + \frac{s(b - m)}{\sqrt{\sigma + \epsilon}} + b_{bn}
​   得到融合之后的权值:
Wconv=Wsσ+ϵ W&#x27;_{conv} = W\frac{s}{\sqrt{\sigma + \epsilon}}

b_conv=(bconvm)sσ+ϵ+bbn b\_{conv} = (b_{conv} - m)\frac{s}{\sqrt{\sigma + \epsilon}} + b_{bn}

​   层融合对模型压缩的效果并不明显,对比赛所提供模型,层融合之后大小仅下降3KB。对于显存大小及推理时间的提升未做详细的量化分析。

二、 模型剪枝

​   对于一些权重极小的连接,将其去除几乎不会影响模型的准确性,反而在重训练过程中有助于避免过拟合现象的发生。本次针对该模型的剪枝以阈值修剪:去除绝对值低于$ 1*e^{-10}$的连接。关于阈值的设定,刚开始由于没有经验,不敢设很大。后来测试时发现,阈值设为小于1e61*e^{-6}以内剪枝的数量几乎一致。但是当阈值高到1e21*e^{-2}时就会对模型产生毁灭性的影响。本轮剪枝后模型大小下降也并不明显,大约有340KB左右的下降。而且由于在修剪时需要同时对权值和prototxt文件进行修改,未能做到很好地利用代码自动修剪,因此修剪体验一般。

avatar

三、 删除卷积层

   这个想法实在太过于简单粗暴,几乎不可能成为一个通用的压缩方法。幸运的是本次比赛的模型的conv5冗余较大,直接删除后边四层的影响不大,而且赛后其他同学讨论中提到甚至可以直接删除后边五层。必须承认这个方法来自QQ群中一位同学的分享,并非自己发现。我自己在这一部分的工作主要就是修改代码和prototxt文档。这一方式虽然简单粗暴,却十分有效,直接将我从弃赛的边缘拉到了前60名。该方法的主要提升在于对显存占用和推理计算量的降低。对于模型大小的压缩效果并没有很明显,该步使模型大小减少了大约8.4MB。

四、 奇异值分解

​   奇异值分解(Singular Value Decomposition, SVD)是本次比赛所使用的最主要的压缩手段,效果也是最明显的。

avatar

avatar

​   由于全连接层占据了模型中最大一部分的参数,对全连接层作奇异值分解可以得到极大的压缩收益。因此本次将SVD作为了模型压缩的重点。有几天的工作都花在了测试不同的r值对模型准确率的影响上。并且希望能够找到合适的r使得z恰好处于临界点(0.950或0.900)。最终,本次最好成绩对应的r值为136, 对应的z值为0.958。也许是由于服务器波动等因素,在r=130时,z取值为0.951,却并未得到更加理想的成绩。而使z接近0.9的r值为108,此时z值为0.901,但权值的下降并没能换来足够的显存使用及计算量的改善。这一部分对于运算速度和显存占用有着显著的优化,同时对模型的大小有着更为显著的压缩。在r=130时,模型大小下降了大约190MB, 比原来压缩了大约63%。

五、 其他未成功应用方法

(1)无需数据的卷积网络自动加速

​   无需数据的卷积网络数据加速(Data-free Automatic Acceleration of Convolutional Networks, DAC)是由Xin Li 等人与2018年提出的一种无需重新训练模型的压缩方式【2】。将卷积核分解为Depthwise 及 Pointwise两种,分解运算的核心其实还是奇异值分解。最终可以实现对已训练完成的模型的卷积核分解与替换,以及权值的迁移,而无需重新训练。原文作者在CIFAR-VGG模型上测试,发现替换靠后的卷积层可以在减少较多参数的同时只造成很小的精度损失,而处理较为靠前的卷积层则代价较大。
附上自己实现DAC的代码

https://github.com/baizhenmao95/2019-ZTE-Algorithm-Competition/blob/master/DAC_AGAIN.py

avatar

​   由于论文中给出了该算法的详细步骤,因此我尝试用代码复现了该算法,并对卷积层conv4_5_2进行了替换。虽然最终确实可以得到压缩后的模型,将r取为5时大约能够减少该层一半的参数。但是也许是我的程序实现有误,也许是该算法并不适用于该类模型的压缩,甚至也许是所选的用于替换的卷积层并不合适,总之最终得到的模型与原模型相比,所提取的特征图的余弦距离出现了负数。最后一天我压宝压在了这一算法上,却最终没能实现突破,也是一大遗憾。

(2) Octave Conv

​   OctaveConv是前几天刚出现的一个新的方法。一出现就得到了许多深度学习相关的学者和媒体的大力推荐。而其简单有效的思想也让我印象深刻。因此十分希望能够复现其中的结果,并在此次比赛中为我所用。作为一个新的方法,使用它的优势在于我可能会是比赛中为数不多的使用者,也就有可能得到几乎独一无二的改善。但是劣势在于,过于新鲜的方法意味着对其的解读与代码的实现方面可能很有限。因此在将近一周的时间内我只能对照着论文原文【3】和某个MXnet版本的第三方复现代码【4】学习。它即插即用、无需更改网络结构的特性非常吸引人,而其通俗易懂的比喻也降低了对算法的原理及其效果的理解难度。可惜最终我还是意识到它似乎是一个需要重新训练的算法,最终只能遗憾放弃。但是在之后的科研工作中也许能够真正地用到。因此也算是一项不错的收获。

avatar

(3)DEEP COMPRESSION

​   这一压缩操作来自于一篇很经典的模型压缩论文【5】以及一个caffe版本的第三方复现代码。主要做了基于Kmeans聚类的量化来压缩卷积和全连接层的权重。经过压缩后得到了一个大约只有8MB的numpy模型文件。这对于嵌入式设备的使用非常友好。但是也许由于参数选取不合适等原因造成了z值的大幅下降,最终只能弃用。但该算法的实用意义也许要大于比赛意义。

附参考资料:
【1】 https://blog.csdn.net/u013597931/article/details/85697008
【2】https://arxiv.org/pdf/1812.08374.pdf
【3】https://export.arxiv.org/pdf/1904.05049
【4】https://github.com/terrychenism/OctaveConv
【5】https://arxiv.org/pdf/1510.00149v5.pdf

2019-04-02 23:25:30 wills798 阅读数 303
  • U-Net图像语义分割实战:训练自己的数据集

    U-Net是一种基于深度学习的图像语义分割方法,尤其在医学图像分割中表现优异。 本课程将手把手地教大家使用labelme图像标注工具制作自己的数据集,生成Mask图像,并使用U-Net训练自己的数据集,从而能开展自己的图像分割应用。 本课程有三个项目实践: (1) Kaggle盐体识别比赛 :利用U-Net进行Kaggle盐体识别 (2) Pothole语义分割:对汽车行驶场景中的路坑进行标注和语义分割 (3) Kaggle细胞核分割比赛 :利用U-Net进行Kaggle细胞核分割 本课程使用keras版本的U-Net,在Ubuntu系统上用Jupyter Notebook做项目演示。 包括:数据集标注、数据集格式转换和Mask图像生成、编写U-Net程序文件、训练自己的数据集、测试训练出的网络模型、性能评估。 本课程提供项目的数据集和Python程序文件。

    864 人正在学习 去看看 白勇

原文链接: https://mp.weixin.qq.com/s/I5XgYrPCCGyfV2qTI0sJhQ

深度神经网络自出现以来,已经成为计算机视觉领域一项举足轻重的技术。其中,ImageNet 图像分类竞赛极大地推动着这项新技术的发展。精确计算水平取得了稳步的增长,但颇具吸引力的模型应用尚未得到合理的利用。

本文将综合分析实际应用中的几项重要指标:准确度、内存占用、参数、操作时间、操作次数、推理时间、功耗,并得出了以下几项主要研究结论:

  1. 功耗与批量大小、体系结构无关;

  2. 准确度与推理时间呈双曲线关系;

  3. 能量限制是最大可达准确度和模式复杂度的上限;

  4. 操作次数可以有效评估推理时间。

ImageNet 历届冠军架构评析指标

自从2012年的 ImageNet 竞赛上,Alexnet取得突破发展,成为第一个应用深度神经网络的应用,其他关于DNN的更复杂的应用也陆续出现。

图像处理软件分类挑战赛的终极目标是,在考虑实际推理时间的情况下,提高多层分类框架的准确度。为了达到这个目标,就要解决以下三方面的问题。第一,一般情况下,我们会在每个验证图像的多个类似实例中运行一个给定模型的多个训练实例。这种方法叫做模型平均或DNN集成,可以极大提高推理所需的计算量,以获得published准确度。第二,不同研究报告中对验证图像做的预估模型(集合)的操作次数不一样,模型选择会受到影响,因此不同的抽样方法(以及取样集合的大小不同)得出的报告准确度结果就会有所偏差。第三,加速推理过程是模型实际应用的关键,影响着资源利用、功耗以及推理延迟等因素,而目前尚无方法使推理时间缩短。

本文旨在对过去4年图像处理软件分类挑战赛上出现的不同种类的先进的DNN架构做对比,从计算需要和准确度两个角度做分析,主要比较这些架构与资源利用实际部署相关的多个指标,即准确度、内存占用、参数、操作时间、操作次数、推理时间、功耗。

文章主要目的是通过分析,强调这些指标的重要性,因为这些指标是优化神经网络实际部署与应用的基本硬性限制条件。

评析方法

为了比较不同模型的质量,我们收集了文献中的一些数据,分析发现不同的抽样方法得出的结论也不一样。比如,VGG-16和GoogleNet 的central-crop误差分别是8.7%和10.07%,表明VGG-16性能优于googleNet,而用10-crop抽样,则误差分别是9.33%和9.15%,VGG-16又比GoogleNet差了。于是,我们决定基于分析,对所有网络重新评估,使用单个central-crop抽样方法。

图1: Top1 vs. 网络.  Single-crop top-1 用最高评分体系检测准确度。上图中不同的配色方案表示不同的架构和作者。注意,同组网络共享相同的色相,比如所有的ResNet系列都是用粉色系表示的。


图 2: Top1 vs. 操作、数量大小、参数  Top-1 one-crop 准确度与单向前进传递所需操作次数的对比。图中气泡大小与网络参数数量成正比;右下角记录的是从5*106 到155*106参数值的历史最大值;所有数据都共享一个y轴,灰色点表示气泡中心的值。

我们使用 cuDNN-v5和CUDA-v8配置的Torch 7来做推理时间和内存占用测算。所有的试验都使用的是JstPack-2.3 NVIDIA Jetson TX1,内置视觉计算系统,64-bit ARM  A57 CPU。

使用这种限量级的设备是为了更好地强调网络架构的不同,主要是因为使用现存的大多数GPU,比如NVIDIA K40或者Titan X得出的结果基本都一样。为了测算功耗,我们使用的是Keysight 1146B Hall电流探头,内置Keysight MSO-X 2024A 200MHz 数字显波器,抽样周期2s,采样率50kSa/s。该系统由 Keysight E3645A GPIB数控直流电源供电。

具体结果

比较了以下 DDN:

  • AlexNet (Krizhevsky et al., 2012);batch normalised AlexNet (Zagoruyko, 2016);batch normalised Network In Network (NIN) (Lin et al., 2013);

  • ENet (Paszke et al., 2016) for ImageNet (Culurciello, 2016);

  • GoogLeNet (Szegedy et al., 2014);

  • VGG-16 and -19 (Simonyan & Zisserman, 2014);

  • ResNet-18, -34, -50, -101 and -152 (He et al., 2015);

  • Inception-v3 (Szegedy et al., 2015) 以及 Inception-v4 (Szegedy et al., 2016)。

1. 准确率(Accuracy)

图 1 展示了提交给 ImageNet 挑战赛的架构的 1-crop 准确率,最左边的是 AlexNet,最右边的是 Inception -v4。最新的 ResNet 和 Inception 架构相比其他架构准确率至少高 7%。本文中,我们使用不同的颜色区分不同的架构和他们的作者,同一个网络的色系相同,例如粉色系的都是 ResNet。

图2 则提供了各网络更详细的准确率值,将计算成本和网络参数的数量可视化呈现。首先非常明显的是,VGG 不管从计算需求还是参数数量方面来说,都是迄今为止最昂贵的架构,尽管它已经被广泛应用于许多应用程序。VGG 的16层和19层的实现实际上与其他所有网络都是隔绝的。其他的架构形成了一条斜线,到 Inception 和 ResNet 时,这条线开始变平缓。这表明这些模型在该数据集上到达一个拐点。在这个拐点上,计算成本(复杂性)开始超过准确率上的好处。

2. 推理时间(Inference Time)

上图(图3)显示了各架构在每个图像上的推理时间,作为一个图像批大小(从1到64)函数。我们注意到 VGG 处理一张图像所需时间约1/5秒,这使它在 NVIDIA TX1 上实时应用的可能性较小。AlexNet 的批大小从1到64的变化中,处理速度提升了3倍,这是由于它的完全连接层的弱优化,这个发现令人惊讶。

3. 功耗(Power)

由于电流消耗的高频率波动,功耗的测量相当复杂,需要高采样电流读出以避免混淆。在本研究中,我们使用的测量工具是带电流探头的 200 MHz 数字示波器。如上图所示,功耗多数情况下与批大小无关。由图3可见,AlexNet (批大小为1)和 VGG(批大小为2)的低功耗与较慢的推理时间相关。

4 内存(Memory)

分析使用 CPU 和 GPU 共享内存的 TX1 设备的系统内存消耗得到的结果由下图可见,最初最大系统内存使用情况是不变的,随着批大小增加,内存消耗增大。这是由于网络模型的初始内存分配以及批处理时的内存需求随着图像数量的增加而成比例地增加。

   

从上图中我们注意到,对规模小于 100 MB的网络,初始内存分配不会小于 200 MB,而且随后呈现为一条斜率为1.3的线性函数。

5 运算(Operations)

在神经网络加速器的自定义实现中,运算量(operation count)对于预估推理时间和硬件电路体积是必要的。

分析发现,对批大小为16的图像,每个图像的运算量和推理时间之间存在线性关系。因此,在设计网络时,可以控制运算量,以使处理速度保持在实时应用或资源有限的应用的可接受范围内。

6. 运算和功耗

分析功耗和给定模型所需的运算次数之间的关系后,我们发现不同架构之间没有特定的 power footprint(见上图)。当达到完全的资源利用时,通常批大小较大,所有网络的额外消耗大致为 11.8 W,标准偏差为 0.7 W,空闲功率为 1.30 W。这是资源完全利用时的最大系统功耗。因此,如果功耗是我们要关注的点之一,例如电池设备限制,可以简单地选择满足最低功耗要求的最慢的架构。

7 准确率和吞吐量

我们注意到,在单位时间里,准确率和推理数量之间存在非平凡的线性上限。下图显示,对于给定的帧速率,可以实现的最大准确率与帧速率本身形成线性比例。这里分析的所有网络均来自公开出版论文,并且已经得到其他研究团队的独立训练。准确率的线性拟合显示了所有架构的准确率与速度之间的关系。

此外,选定一个推理时间,可以得出资源充分利用条件下理论上的最大准确率。由于功耗固定,我们甚至可以进一步得出能耗限制下的最大准确率,这可以作为需要在嵌入式系统上运行的网络的基本设计因素。由于没有了扰流器,考虑前向推理时间时,准确率与吞吐量之间的线性关系转变为双曲线关系。那么,假设运算量与推理时间是线性关系,准确率对网络需要的运算量则具有双曲线依赖性(hyperbolical dependency)。

8 参数使用

我们已经知道,DNN 在利用全部学习能力(参数数量/自由度)方面非常低效。Han et al., 2015 的研究利用 DNN 的这个缺陷,使用权重剪枝(weights pruning)、量化(quantisation)和变长编码(variable-length symbol encoding)将网络规模减小了50倍。值得注意的是,使用更高效的架构能够产生更紧凑的呈现。如上图所示,虽然 VGG 比 AlexNet 的准确率更高(图1),但其信息密度不如 AlexNet。这意味着在 VGG 架构中引入的自由度带来的准确率上的提高不大。

结语

本文从准确性、内存占用、参数、运算量、推理时间和功耗方面,对 ImageNet 竞赛中多个先进深层神经网络进行了分析,从而对设计用于实际应用的高效神经网络提供参考并优化资源,因为在实际部署中我们能使用的资源往往十分有限。从上文可知,神经网络的精度和推理时间呈双曲关系:准确度的微量增加也会花费大量的计算时间。此外,网络模型的运算量能有效估计推理所需要的时间。

这也是我们为 ImageNet 创建 ENet(Efficient-Network)的原因。ENet 是当前对参数空间利用率最好的架构。

论文下载地址:https://arxiv.org/pdf/1605.07678v3.pdf

2019-12-06 12:58:45 weixin_42137700 阅读数 32
  • U-Net图像语义分割实战:训练自己的数据集

    U-Net是一种基于深度学习的图像语义分割方法,尤其在医学图像分割中表现优异。 本课程将手把手地教大家使用labelme图像标注工具制作自己的数据集,生成Mask图像,并使用U-Net训练自己的数据集,从而能开展自己的图像分割应用。 本课程有三个项目实践: (1) Kaggle盐体识别比赛 :利用U-Net进行Kaggle盐体识别 (2) Pothole语义分割:对汽车行驶场景中的路坑进行标注和语义分割 (3) Kaggle细胞核分割比赛 :利用U-Net进行Kaggle细胞核分割 本课程使用keras版本的U-Net,在Ubuntu系统上用Jupyter Notebook做项目演示。 包括:数据集标注、数据集格式转换和Mask图像生成、编写U-Net程序文件、训练自己的数据集、测试训练出的网络模型、性能评估。 本课程提供项目的数据集和Python程序文件。

    864 人正在学习 去看看 白勇

文/编辑 | 言有三

自从Google提出AutoML技术以来,已经过去了好几年了,如今在学术界和工业界都是热点。AutoML在网络结构的搜索上已经取得了非常多的突破,相关的文章,技术博客都已经广为流传,那么除了在网络结构本身的搜索上,AutoML技术对于深度学习模型的优化还有哪些贡献呢?本文就来简要总结这个问题。

1 数据增强

数据增强是深度学习模型训练的必备良药,写论文刷比赛提指标的大杀器。传统的数据增强方法以各类通过参数控制的预设几何变换和颜色变换为主,如果让模型针对具体的任务自动学习数据增强,理论上会更加智能,这便是基于AutoML的数据增强技术,它主要是用于自动学习数据增强策略,包括选择哪一个操作,什么时候应用该操作。

除了网络搜索(NAS),AutoML对深度学习模型优化还有哪些贡献?

AutoML与数据增强

「AutoML」如何选择最合适的数据增强操作

2 激活函数

激活机制是一个网络非线性表达能力的来源,早期研究人员已经设计出了不少的激活函数,从sigmoid到relu系列,随着AutoML技术的发展,现在研究人员开始使用搜索技术来进行设计,提出了Swish及更多的变种

除了网络搜索(NAS),AutoML对深度学习模型优化还有哪些贡献?

AutoML与激活函数

「AutoML」激活函数如何进行自动学习和配置

3 归一化方法

数据经过归一化和标准化后可以加快梯度下降的求解速度,这是Batch Normalization等技术非常流行的原因,它使得可以使用更大的学习率更稳定地进行梯度传播,甚至增加网络的泛化能力。当前归一化方法非常多,那怎么选择呢?每一个网络层中都使用同样的归一化,这是最优的配置吗?不如交给AutoML来自动配置。

除了网络搜索(NAS),AutoML对深度学习模型优化还有哪些贡献?

AutoML与归一化

「AutoML」归一化(Normalization)方法如何进行自动学习和配置

4 优化方法

要成功训练一个深度学习模型,正确的优化策略是非常重要的,如果使用不当结果会产生很大的差异,然而要熟练使用,需要大量的工程经验和深厚的理论基础,那么能不能使用AutoML来减轻这个负担呢?

除了网络搜索(NAS),AutoML对深度学习模型优化还有哪些贡献?

AutoML与优化方法

「AutoML」优化方法可以进行自动搜索学习吗?

5 优化目标

一个有效的损失函数在深度学习任务中起了关键作用,然而损失函数都是人为设定,不仅需要有经验的人员进行反复尝试,也往往只能获得次优的方案,如果可以让模型自动对优化目标进行学习,将有望以更低的成本学习到更优的模型。

除了网络搜索(NAS),AutoML对深度学习模型优化还有哪些贡献?

AutoML与优化目标

「AutoML」损失函数也可以进行自动搜索学习吗?

6 模型剪枝

我们给大家介绍过各种各样的模型压缩技巧,其中模型剪枝是一个研究已久,也非常考验工程素质的方向,那能否使用AutoML技术来进行剪枝呢?

除了网络搜索(NAS),AutoML对深度学习模型优化还有哪些贡献?

AutoML与模型剪枝

「AutoML」如何使用强化学习进行模型剪枝?

7 模型量化

模型量化是深度学习模型压缩的大杀器,当前大部分框架对整个模型采用同样的位宽进行量化,以HAQ(Hardware-Aware Automated Quantization with Mixed Precision)为代表的方法则是一个自动化的混合精度量化框架,使用AutoML技术让每一层都学习到了适合该层的量化位宽,又有什么优势呢?

除了网络搜索(NAS),AutoML对深度学习模型优化还有哪些贡献?

AutoML与模型量化

「AutoML」强化学习如何用于模型量化?

8 更多模型设计与优化内容

我们公众号最多的就是模型设计相关的内容,包括主流的模型设计思想,三次长达一个小时的阿里天池模型设计直播,知识星球的网络结构1000变板块,感兴趣的粉丝可以自取。

除了网络搜索(NAS),AutoML对深度学习模型优化还有哪些贡献?

主流模型设计

2018-10-03 21:56:07 weixin_42749767 阅读数 5883
  • U-Net图像语义分割实战:训练自己的数据集

    U-Net是一种基于深度学习的图像语义分割方法,尤其在医学图像分割中表现优异。 本课程将手把手地教大家使用labelme图像标注工具制作自己的数据集,生成Mask图像,并使用U-Net训练自己的数据集,从而能开展自己的图像分割应用。 本课程有三个项目实践: (1) Kaggle盐体识别比赛 :利用U-Net进行Kaggle盐体识别 (2) Pothole语义分割:对汽车行驶场景中的路坑进行标注和语义分割 (3) Kaggle细胞核分割比赛 :利用U-Net进行Kaggle细胞核分割 本课程使用keras版本的U-Net,在Ubuntu系统上用Jupyter Notebook做项目演示。 包括:数据集标注、数据集格式转换和Mask图像生成、编写U-Net程序文件、训练自己的数据集、测试训练出的网络模型、性能评估。 本课程提供项目的数据集和Python程序文件。

    864 人正在学习 去看看 白勇

背景


最近实验室要参加一个目标检测的比赛,这段时间一直在跑ssd模型,最开始根据作者给的文档成功编译后,可以在VOC数据集上进行训练。由于要用比赛官方的数据集,因此做了几天的数据集,然后拿自己的数据集训练的时候,出现了以下报错:Check failed: a <= b (0 vs. -1.192093-07)

在这里插入图片描述

去网上搜了相关的解决方法,全都是说把math_functions.cpp第250行注释掉,重新编译,这种方案一看就不靠谱,而且也没人说个所以然,但是还是抱着试一试的心态照做了,果然又出现了新的bug。查了几天的资料也没找到解决方案,一开始我怀疑可能是我的数据集做的有问题,然后我又重新在VOC数据集上训练,结果会出现同样的问题,联想到前两天实验室服务器重装了系统,然后cuda从8.0换到了9.1版本,会不会是这个原因导致了现在的报错呢?但是因为实验室服务器是大家共用的,把cuda改回到8.0版本可能给其他人带来困扰,刚好实验室有其他同学在搞nvidia docker,干脆直接用nvidia docker来跑模型,就不用考虑环境问题了。

什么是nvidia docker


介绍nvidia docker之前,首先要了解什么是docker。

Docker 是一个开源的应用容器引擎,基于 GO语言并遵从Apache2.0协议开源。

Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,可以实现虚拟化。

Docker所代表的容器虚拟化技术属于操作系统级虚拟化:内核通过创建多个虚拟的操作系统实例(内核和库)来隔离不同的进程。并且传统虚拟化技术是在硬件层面实现虚拟化,增加了系统调用链路的环节,有性能损耗;容器虚拟化技术以共享Kernel的方式实现,几乎没有性能损耗。

这里可以将容器理解为一种沙盒。每个容器内运行一个应用,不同的容器相互隔离,容器之间可以建立通信机制。容器的创建和停止都十分快速(秒级),容器自身对资源的需求十分有限,远比虚拟机本身占用的资源少。

关于Docker更详细的介绍,请参照几张图帮你理解docker基本原理及快速入门,感觉介绍的很不错

docker一般服务于基于cpu 的应用,而我们的深度学习模型是跑在gpu上面的,因此需要用nvidia docker。nvidia docker的运行需要基于一定的硬件环境,需要安装nvidia driver,docker容器本身并不支持nvidia gpu。最开始的解决方法是在容器内部安装nvidia driver,然后通过设置相应的设备参数来启动container,但是这样做带来一个弊端就是可能导致image无法共享,因为宿主机的driver的版本必须完全匹配容器内的driver版本,很可能本地机器的不一致导致每台机器都需要去重复操作,这很大的违背了docker的初衷。nvidia docker实际上是一个docker plugin,它在docker上做了一层封装,对docker进行调用,类似一个守护进程,发现宿主机驱动文件以及gpu 设备,并且将这些挂载到来自docker守护进程的请求中,以此来支持docker gpu的使用。

安装docker


  1. GPU driver安装

nvidia官网下载安装对应型号的显卡驱动:链接
如果安装成功,在终端中输入 lspci | grep -i nvidia ,会显示自己的NVIDIA GPU版本信息

  1. CUDA安装

实验室服务器是ubuntu 18.04版本,可以直接sudo apt install nvidia-cuda-toolkit安装

  1. docker安装
  • 安装必要的一些系统工具
sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
  • 安装GPG证书
curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
  • 写入软件源信息
sudo add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
  • 更新并安装 docker-ce
sudo apt-get -y update
sudo apt-get -y install docker-ce
  • 验证
sudo service docker status 			#或者sudo systemctl status service.docker 检查Docker服务的状态 
sudo docker run hello-world			#测试Docker安装是否成功

在这里插入图片描述
在这里插入图片描述

  1. nvidia-docker安装
  • 如果之前安装过docker1.0版本,需要先删掉该版本和之前创建的容器
docker volume ls -q -f driver=nvidia-docker | xargs -r -I{} -n1 docker ps -q -a -f volume={} | xargs -r docker rm -f
sudo apt-get purge -y nvidia-docker
  • 添加代码仓库
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | \
sudo apt-key add -
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | \
sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update
  • 安装docker 2
sudo apt-get install -y nvidia-docker2
sudo pkill -SIGHUP dockerd
  • 测试
docker run --runtime=nvidia --rm nvidia/cuda:9.0-base nvidia-smi
  1. 安装过程中遇到的问题

网上有的教程会设置阿里云加速器,是因为官方Docker Hub网络速度较慢,所以使用阿里云提供的Docker Hub,然后需要配置阿里云加速器。具体步骤如下:

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-‘EOF’ 
{ 
   “registry-mirrors”: [“https://fird1mfg.mirror.aliyuncs.com“] 
} 
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

结果我在重启docker服务的时候,产生了如下报错:

docker.service - LSB: Create lightweight, portable, self-sufficient containers
Loaded: loaded (/etc/init.d/docker; generated)
Active: failed (Result: exit-code) since Wed 2018-09-26 10:11:16 CST; 28s ago
Docs: man:systemd-sysv-generator(8)
Process: 18639 ExecStart=/etc/init.d/docker start (code=exited, status=1/FAILULURE)
Main PID: 15621 (code=exited, status=1/FAILURE)

9月 26 10:11:16 archlab-X10DRG systemd[1]: Starting LSB: Create lightweight, portable, self-sufficient containers....
9月 26 10:11:16 archlab-X10DRG docker[18639]:  * /usr/bin/dockerd not present or not executable
9月 26 10:11:16 archlab-X10DRG systemd[1]: docker.service: Control process exited, code=exited status=1
9月 26 10:11:16 archlab-X10DRG systemd[1]: docker.service: Failed with result 'exit-code'.
9月 26 10:11:16 archlab-X10DRG systemd[1]: Failed to start LSB: Create lightweight, portable, self-sufficient containers

根绝报错的第二行,发现是dockerd除了问题,dockerd是docker的守护进程,现在提示不存在或不可用,然后我执行了sudo dockerd,打印出了以下报错信息

unable to configure the Docker daemon with file /etc/docker/daemon.json: invalid character ‘h’ after object key

这说明docker的配置文件除了问题,打开daemon.json文件,果然发现刚才设置阿里云加速器的时候,写入的语句有问题,应该是我直接复制粘贴导致的问题,改正之后docker服务可以正常启动了。

用nvidia docker进行训练


  1. 拉取镜像(这里拉取了阿里云的一个镜像,里面自带了编译好的caffe,不过由于在实验室的宿主机上已经有编译好的caffe,可以直接将宿主机的目录挂载到容器中,这个后面有说)
sudo nvidia-docker pull registry.cn-hangzhou.aliyuncs.com/docker_learning_aliyun/caffe:v1
  1. 查看拉取的镜像信息
sudo nvidia-docker images

在这里插入图片描述

  1. 利用拉取的镜像启动容器,并把宿主机的caffe目录挂载到容器上
sudo nvidia-docker run -it –v $CAFFE_ROOT:/workspace 4e33(镜像id前4位即可)/bin/bash

这样就启动了一个容器,并且把caffe目录挂载到了容器的/workspace下。这样操作的好处是,训练完的数据可以直接存放在宿主机,省略了从容器中拷贝的繁琐步骤。

剩下的就是将caffe模型跑起来等待结果就可以了。

主流机器学习模型代码

博文 来自: alicelmx
没有更多推荐了,返回首页