使用循环神经网络训练语言模型(从简单起手、歌词生成器,爬虫+GRU循环网络)

使用循环神经网络训练语言模型(从简单起手、song_words生成器,爬虫+GRU循环网络)
第一部分:song_words获取(本次主要是训练语言模型,度娘random搜的公开的song_words下载网站,编写爬虫程序自动下载song_words)
第二部分:网络构建,song_words生成测试(使用GRU门控循环网络,进行语言模型训练,根据预先给定词,自动向后编写song_words)

结果展示: 输入 “生命就” 3个字,
自动生成后续song_words: "生命就像找 让我们彼此坦诚 现在或永不 感觉越来越虚无 就像我越来越硬可与你无关 我发现了一些迹象 让我疼痛"

第一部分:

  1. 爬虫

    import requests
    import re
    import time
    
    
    # 下载本页song_words信息
    def download_song_words(urls, save_file='./data/', base_url=''):
        print(urls)
        for url in urls:
            print(f'正在下载{url[-1]}页song_words')
            text = requests.get(url, headers=gen_cookies()).text
            p = re.compile('<a class="adw" href="(.*?)">下载</a>')
            current_content_url = p.findall(text)
            print(current_content_url)
            for i in current_content_url:
                url_ = base_url + i
                res = requests.get(url_, headers=gen_cookies())
                file_name = save_file + str(time.time()).replace('.', '') + '.txt'
                with open(file_name, mode='wb') as fp:
                    fp.write(res.content)
            time.sleep(6)
    
    
    # cookie 编码
    def gen_cookies():
        timestamp = int(time.time())
        headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36',
                   'Cookie': f'PHPSESSID=am6l1aa2v5pupmjr5bfon76089; UM_distinctid=17fe7b8e7a8825-05de77a6e9159-1f343371-e1000-17fe7b8e7a9ce3; Hm_lvt_ffe211dccf4f4c9feb29d0362edaf695=1648860129; __gads=ID=868ae96b248e2d81-22ca232e72d10070:T=1648860126:RT=1648860126:S=ALNI_MZwUA1zfK19_GunP2kvnXMFc_dQqg; CNZZDATA2194552=cnzz_eid%3D42194971-1648854806-null%26ntime%3D1648865652; Hm_lpvt_ffe211dccf4f4c9feb29d0362edaf695={timestamp}',
                   'Host': 'www.22lrc.com'}
        return headers
    
    
    if __name__ == '__main__':
        
        # 输入要下载song_words的singer名称
        singer_names = ['汪xx', 'xx华', 'xx友', 'xx伦', 'xx盛', '周xx', '张xx', 'xx迅']
        save_file = './data/'
        for singer_name in singer_names:
            start_url = f'xxx' # 因博客提示涉及版权问题,网址就不能公布了,度娘上可以自行搜索
            base_url = 'xxxxx'
    
            response = requests.get(url=start_url, headers=gen_cookies())
            text = response.text
            relu = re.search('keyword=(.*?)&radio', response.url).group(1)
    
            # 获取所有用于翻页的页码链接
            p = re.compile(f"<a href='(\?keyword={relu}&radio=1&page=\d)'>\d</a>")
            page_url_list = p.findall(text)
            print(page_url_list)
            page_url_list = ['http://www.22lrc.com/search.php'+i for i in page_url_list]
            page_url_list.append(start_url)
    
            time.sleep(6)
            # 下载所有页码的songs
            download_song_words(urls=page_url_list, save_file=save_file, base_url=base_url)
    
    
  2. 爬取结果展示
    在这里插入图片描述
    song_words内容:
    在这里插入图片描述

  3. 数据清洗

    import pandas as pd
    import re
    import glob
    
    
    def is_in_text(words, text):
        # 去除指定词
        for i in words:
            if i in text or text in i:
                return True
        # 去除日期
        if re.search('\d{4}年\d{2}月\d{2}日', text):
            return True
    
    
    res_dict = {'songs': [], 'singer': [], 'song_words': []}
    
    for path in glob.iglob('./data/*.txt'):
        fp = open(path, mode='r', encoding='gbk')
        text_list = fp.readlines()
        song_name = re.search('\[ti:(.*?)\]', ''.join(text_list)).group(1)
        singer = re.search('\[ar:(.*?)\]', ''.join(text_list)).group(1)
        res_dict['songs'].append(song_name)
        res_dict['singer'].append(singer)
        
        # 清洗掉没用的词
        stop_words = ['汪xx', 'xx华', 'xx友', 'xx伦', 'xx盛', '周xx', '张xx', 'xx迅',
                      '词曲', '编曲', 'song_words', '编辑', 'LRC', 'www', 'QQ', '词', '曲', '原唱', '现场', 'ProgramPrgram',
                      'singer', '第八期', '演唱会', '定位制作人 川剧帮腔 念白 音乐总监 乐团 ', 'SP',
                      '出品', '出品人', '总策划', '监制', '推广', '演唱 ', '母带工作室', '音乐制作人',
                      '电影 Bass 弦乐团 录音制作混音师 录音棚', '小提琴1 小提琴2 中提琴 大提琴', '键盘',
                      '弦乐监制 笛子 二胡 人声录音室 人声录音师 和声 音乐制作 特别鸣谢 音乐发行', '录音工程师 混音工程师 制作发行',
                      '制作人总监', '萨克斯小号长号弦乐钢琴吉他贝斯鼓合音童声合唱团网络剧演唱',
                      '录音师录音室混音师混音室母带工程师企划统筹制作OP发行公司',
                      '贝司弦乐编写弦乐演奏弦乐录音弦乐录音室']
    
        content_list = []
        for text in text_list:
            content = re.search('\[\d\d:\d\d\.\d\d\]([\w ]+)', text)
            if content and not is_in_text(stop_words, content.group(1)):
                content_list.append(re.sub('[a-zA-Z]+', '', content.group(1)))
    
        res_dict['song_words'].append(re.sub('[\s]{2,}', ' ', ' '.join(content_list)))
    
    data = pd.DataFrame(res_dict)
    data['song_words'] = data['song_words'].apply(lambda x: x if len(x) > 20 else None)
    data.dropna(inplace=True)
    data.to_excel('songs.xlsx', index=False)
    
    
  4. 数据清洗结果展示
    在这里插入图片描述

第二部分
  1. 导包

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    import jieba
    import random
    
    import torch
    import torch.nn as nn
    from torch.optim import Adam
    from torch.nn import CrossEntropyLoss
    from torch.nn.utils.rnn import pack_padded_sequence,pad_packed_sequence
    
  2. song_words数据处理

    # 2.1 读入爬取数据(这里只训练了前10首songs)
    data = pd.read_excel('../input/songs/songs.xlsx').query('singer=="汪xx"')[:10]
    data.head()
    
    '''
    songs	          singer	song_words
    0	闪xxx子	xx	在xx些xxxxxxxxxx 看...
    1	现xxx不	xx	生xx越xxxxx 我不...
    2	今xxx里	xx	此刻xxxxxxxx之殇 当xxx驶入...
    4	x言	      xx	万一xxxxxxx学会了给你按摩...
    5	飞xx高	xx	生xxxxxxx  生活...
    '''
    
    # 2.2 分字(songs太少,使用字符粒度训练语言模型)
    data['分字'] = data['song_words'].apply(lambda x:[i for i in x])
    data.head()
    
    '''
          songs	  singer	  song_words	                        分字
    0	闪xx子	xx	在xxxx看...	[在, xxxxx, 光, ...]
    1	现xxx不	xx	生xxxxx 我不...	[生, 活,xxxx 越, 性, ...]
    2	今xxxx里	xx	此刻xxxx驶入...	[此, xx, 我, ...]
    
    '''
    
    # 2.3 构建词汇表vocab
    class Vocab:
        
        def __init__(self,texts):
            self.texts = texts
            self.gen_vocab()
            self.gen_vocab_len()
        
        def gen_vocab(self):
            self.vocabs = set()
            for i in self.texts:
                 self.vocabs.update(i)
            self.char_to_idx = dict(zip(self.vocabs,range(len(self.vocabs))))
            self.idx_to_char = dict([(self.char_to_idx.get(i),i)for i in self.char_to_idx])
            
        def gen_vocab_len(self):
            self.vocab_len = len(self.char_to_idx)
           
    vocab = Vocab(data['分字'])
    # 词汇总量
    print(vocab.vocab_len)
    # 词汇
    print(vocab.vocabs)
    # 词汇字典
    print(vocab.char_to_idx)
    
    '''
    527
    
    '雾', '醒', '酷', '抛', '彼', '烟', '头', '傲', '究', '当', '幸', '光', '卡', '同', '浸', '坚', '色', '什', '厌', '卷', '烈', '许', '惧', '憎', '友', '得', '体', '微', '徒', '厮', '忆', '痕', '先', '墟'...}
    
    {'雾': 0, '醒': 1, '酷': 2, '抛': 3, '彼': 4, '烟': 5, '头': 6, '傲': 7, '究': 8, '当': 9, '幸': 10, '光': 11, '卡': 12, '同': 13, '浸': 14, '坚': 15, '色': 16, '什': 17, '厌': 18, '卷': 19, '烈': 20, '许': 21, '惧': 22, '憎': 23, '友': 24, '得': 25, '体': 26, '微': 27, '徒': 28, '厮': 29, '忆': 30, '痕': 31, '先': 32, '墟': 33, '翔': 34, '隔': 35, '捧': 36, '颗': 37...}
    '''
    
    # 2.4 字符编码
    data['分字_index'] = data['分字'].apply(lambda x: [vocab.char_to_idx.get(i) for i in x])
    data['分字_index']
    
    '''
    0     [346, 52, 375, 331, 215, 304, 11, 344, 212, 41...
    1     [418, 486, 42, 110, 42, 133, 406, 212, 407, 42...
    2     [274, 408, 412, 436, 346, 412, 313, 420, 372, ...
    4     [270, 517, 413, 301, 142, 345, 412, 377, 386, ...
    5     [418, 414, 407, 426, 212, 517, 130, 332, 120, ...
    6     [301, 142, 345, 111, 118, 377, 52, 375, 64, 49..
    '''
    
  3. 构建序列数据集

    # 3. 构建序列训练集
    # 3.1 参考李沐老师,序列生成函数(一个batch之间上下序列语义相连,一个batch之间序列语义随机,具体如下验证)
    def seq_data_iter_sequential(corpus, batch_size, num_steps):
        """Generate a minibatch of subsequences using sequential partitioning."""
        # Start with a random offset to partition a sequence
        offset = random.randint(0, num_steps)
        num_tokens = ((len(corpus) - offset - 1) // batch_size) * batch_size
        Xs = torch.tensor(corpus[offset: offset + num_tokens])
        Ys = torch.tensor(corpus[offset + 1: offset + 1 + num_tokens])
        Xs, Ys = Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)
        num_batches = Xs.shape[1] // num_steps
        for i in range(0, num_steps * num_batches, num_steps):
            X = Xs[:, i: i + num_steps]
            Y = Ys[:, i: i + num_steps]
            yield X,Y
            
            
    def seq_data_iter_random(corpus, batch_size, num_steps): 
        """使⽤随机抽样⽣成⼀个⼩批量⼦序列"""
        # 从随机偏移量开始对序列进⾏分区,随机范围包括num_steps-1
        corpus = corpus[random.randint(0, num_steps - 1):]
        # 减去1,是因为我们需要考虑标签
        num_subseqs = (len(corpus) - 1) // num_steps
        # ⻓度为num_steps的⼦序列的起始索引
        initial_indices = list(range(0, num_subseqs * num_steps, num_steps))
        # 在随机抽样的迭代过程中,
        # 来⾃两个相邻的、随机的、⼩批量中的⼦序列不⼀定在原始序列上相邻
        random.shuffle(initial_indices)
        
        def data(pos):
            # 返回从pos位置开始的⻓度为num_steps的序列
            return corpus[pos: pos + num_steps]
        
        num_batches = num_subseqs // batch_size
        for i in range(0, batch_size * num_batches, batch_size):
        # 在这⾥,initial_indices包含⼦序列的随机起始索引
            initial_indices_per_batch = initial_indices[i: i + batch_size]
            X = [data(j) for j in initial_indices_per_batch]
            Y = [data(j + 1) for j in initial_indices_per_batch]
            yield torch.tensor(X), torch.tensor(Y)
            
    # 3.2 验证序列生成函数
    test_seq = range(10)
    
    seq1 = seq_data_iter_sequential(corpus=test_seq, batch_size=2, num_steps=3)
    for i in seq1:
        print(i[0])
        
     '''
     tensor([[ 3,  4,  5],
            [11, 12, 13]])
     上下两个批次间序列连续的,5-->6;13-->14       
     tensor([[ 6,  7,  8],
            [14, 15, 16]])
     '''
    
    seq2 = seq_data_iter_random(corpus=test_seq, batch_size=2, num_steps=3)
    for i in seq1:
        print(i[1])
    '''
    tensor([[13, 14, 15],
            [ 4,  5,  6]])
     上下两个批次间序列随机       
    tensor([[ 7,  8,  9],
            [10, 11, 12]])
    '''
    
    
    # 3.3 把所有songs串起成一个长字符串
    seq_list = [i for text in data['分字_index'] for i in text]
    
  4. 模型构建

    # 4.1 构建GRU循环网络(参数比LSTM少一点,训练快一点,相比RNN,梯度消失问题好一点)
    class NetGRU(nn.Module):
        
        def __init__(self,num_hiddens,num_layer,vocab_size,per_vocab_embedding_len):
            super().__init__()
            self.embedding = nn.Embedding(num_embeddings=vocab_size,embedding_dim=per_vocab_embedding_len)
            self.gru = nn.GRU(input_size=per_vocab_embedding_len,hidden_size=num_hiddens,num_layers=num_layer
                              ,batch_first=True
    #                           ,dropout=0.5
                             )
            
            self.linear = nn.Linear(in_features=num_hiddens,out_features=vocab_size)
            
        def forward(self,x,state):
            x = self.embedding(x)
            outputs,hidden_state = self.gru(x,state)
            z = self.linear(outputs)
            return z,hidden_state
    
  5. 设置损失和优化器

    # 5.1 设置损失和优化器
    num_layer = 1 # 隐藏层数
    per_vocab_embedding_len = 10 # 每个字嵌入词向量长度
    num_hiddens = 128 # 设置隐藏层尺寸
    net = NetGRU(num_hiddens=num_hiddens,num_layer=num_layer
                 ,vocab_size=vocab.vocab_len,per_vocab_embedding_len=per_vocab_embedding_len) 
    
    loss = CrossEntropyLoss()
    
    opt = Adam(params=net.parameters(),lr=0.001)
    
  6. 训练

    # 6. 训练
    epochs = 50
    
    # 损失优化记录
    res = []
    
    for epoch in range(epochs):
        # 每一轮重新生成一个数据迭代器,因为数据起始偏移具有随机性,所以每轮训练数据序列不一样
        data_train = seq_data_iter_sequential(corpus=seq_list,batch_size=batch_size,num_steps=num_steps) 
        # 初始话,隐藏层状态
        init_hidden_state = torch.zeros(size=(num_layer,batch_size,num_hiddens)) 
        for x,y in data_train:
            z, hidden_state = net(x,init_hidden_state)
            init_hidden_state = hidden_state.detach() # 此处使用的,序列批次之间连续生成,因此整个epoch 隐藏状态可以一直传递
            loss_value = loss(z.reshape(batch_size*num_steps,-1),y.flatten())
            opt.zero_grad()
            loss_value.backward()
            opt.step()
        
        res.append(loss_value.item())
        print(f'第{epoch}轮,损失:{loss_value.item()}')
     
    '''第0轮,损失:5.6674652099609375
    第1轮,损失:5.303648948669434
    第2轮,损失:4.941510200500488
    第3轮,损失:4.330902099609375
    ...
    第47轮,损失:0.014354761689901352
    第48轮,损失:0.11933135986328125
    第49轮,损失:0.014101667329668999
    '''
    
  7. 训练损失曲线

    在这里插入图片描述

  8. song_words生成验证

    # 8. 根据现有的song_wors,往下预测
    pre_words = ['生', '命','就']
    idx2word = dict([(vocab.char_to_idx.get(i),i) for i in vocab.char_to_idx])
    pred_outputs = [] # 预测结果写入
    
    batch_size = 1
    init_hidden_state = torch.zeros(size=(num_layer,batch_size,num_hiddens)) 
    
    # 开始预测
    epochs = 50 # 根据现有的song_words的基础上,往下预测50字符的song_words
    
    # 预热模型,根据已给出的序列,先生成hidden_state,方便向下预测
    for i in pre_words:
        x = torch.tensor([vocab.char_to_idx.get(i)]).reshape(batch_size,-1)
        z,hidden_state = net(x,init_hidden_state)
        init_hidden_state = hidden_state.detach()
        pred_outputs.append(i)
    pred_outputs.append(idx2word.get(torch.argmax(z.flatten()).item()))
        
    for epoch in range(epochs):    
        x = torch.tensor([vocab.char_to_idx.get(pred_outputs[-1])]).reshape(batch_size,-1)
        z,hidden_state = net(x,init_hidden_state)
        init_hidden_state = hidden_state.detach()
        pred_outputs.append(idx2word.get(torch.argmax(z.flatten()).item()))
    
    print(''.join(pred_outputs))
    
    '''
    '生命就像找 让我们彼此坦诚 现在或永不 感觉越来越虚无 就像我越来越硬可与你无关 我发现了一些迹象 让我疼痛'
    '''
    
    
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值