前情提要
本文是传知代码平台
中的相关前沿知识与技术的分享~
接下来我们即将进入一个全新的空间,对技术有一个全新的视角~
本文所涉及所有资源均在传知代码平台可获取
以下的内容一定会让你对AI 赋能时代
有一个颠覆性的认识哦!!!
以下内容干货满满,跟上步伐吧~
💡本章重点
- 针对股票评论的情感分类器
🍞一. 文献参考
下面我将使用Pytorch框架来对《Sentiment analysis method based on sentiment lexicon and Transformer》一文中的Transformer情感分类器进行实现,在这里,我使用现有的2022年英文股评数据集作为初始数据,为其创建情感词典,实现句子转向量并完成Transformer模型的训练过程。
🍞二. 概述
由于Transformer模型的强大表征学习能力,可以在大规模文本数据上进行预训练,并且具有适用性广泛的特点,因此Transformer模型已经被广泛应用于自然语言处理领域,它能够各种任务上取得了优异的表现,包括情感分析。本篇使用股票市场上的股民评论数据作为训练数据,股票市场受到投资者情绪和情感的影响很大,通过对股票评论进行情感分析,可以帮助分析师和投资者更好地了解市场参与者的情绪状态,从而预测市场走势;
同时针对股评的情感分析还能帮助公司和投资者监控舆论动向,帮助投资者更好地理解市场参与者的情绪和看法,为投资决策提供参考。
🍞三.演示效果
众所周知,Transformer模型主要由encoder和decoder两部分组成,它们各自承担着不同的功能和作用。
Encoder:
-
用途:Encoder部分负责将输入序列(比如文本)转换为一系列隐藏表示,捕获输入序列中的信息和特征。
-
区别:每个输入词语都经过embedding层后进入encoder,然后通过多层的自注意力机制和前馈神经网络进行特征提取和编码,最终得到输入序列的表示。
Decoder:
-
用途:Decoder部分负责使用encoder生成的表示来预测输出序列(比如机器翻译中的翻译结果)。
-
区别:与encoder不同,decoder在处理每个位置的时候会利用encoder输出的信息,并且具有额外的掩码机制来确保在预测时不会使用未来的信息。
进行情感分析的Transformer模型通常是在完整的Transformer模型基础上进行微调或修改的,主要是为了适应情感分析任务的特点和需求。在情感分析任务中,通常会对输入文本进行一定的处理,如添加特殊标记或截断,以适应模型输入的要求。此外,可能会在输入文本前后添加特殊标记来指示情感分析任务。而模型的输出是对结果的三分类(或者二分类)结果。
要将文本数据变成能够放入模型中的训练数据集,我们需要基于目前已有的股评文本,构建本地可用的词典,并把文本序列转换成一系列长度一致的词向量,如下图:
对应到代码中则可以分成下面几个步骤:
-
将句中字母小写,情感标签映射到【0,1,2】列表中
-
去除特殊字符
-
去除停用词,这需要导入包含英文停用词的数据包
-
将单词进行词性还原
-
将单词与数字一一对应,实现句子转向量
python
df.text_sentiment.unique()
df.drop_duplicates(inplace=True) ### 删除重复值
pd.factorize(df.text_sentiment) ### 0:Neutral 1:Negative 2:Positive
### 将情感标签映射为【0,1,2】
label = pd.factorize(df.text_sentiment)[0]
### 全部变为小写字母,不保留除'之外标点符号,去除特殊字符
pat = re.compile('[A-Za-z\']+')
def pre_text(text):
text = pat.findall(text) ### 返回列表
text = [w.lower() for w in text]
return text
x = df.text.apply(pre_text)
### 去除停用词
def stop_words_filter(text):
text= [w for w in text if w not in stopwords.words('english') ]
return text
x = x.apply(stop_words_filter)
### 词形还原,利用wordnet
wnl = WordNetLemmatizer()
def return_words(text):
text = [wnl.lemmatize(w) for w in text]
return text
for text in x:
text = [return_words(text)]
### 创建词表,将每个词映射到词表上
word_set = set()
for t in x:
for word in t:
word_set.add(word)
max_word = len(word_set)+2 ### 词表总长+2, 留下unknow和padding,后面万一进入与这些词不同的词就映射到unk上,序列padding的部分就映射到pad上
word_list = list(word_set)
word_index = dict((w,word_list.index(w)+1) for w in word_list)
x = x.apply(lambda t:[word_index.get(w) for w in t])
###统一向量长度,将每句话padding为长度相同的列表
maxlen = max(len(t) for t in x)
pad_x = [t+ (maxlen-len(t))*[0] for t in x]
pad_x = np.array(pad_x)
# 将 ndarray 转换为 DataFrame
df_pad_x = pd.DataFrame(pad_x)
df_label = pd.DataFrame(label, columns=['label'])
df_pad_x_reset = df_pad_x.reset_index(drop=True)
df_label_reset = df_label.reset_index(drop=True)
df_output = pd.concat([df_pad_x_reset, df_label_reset],axis=1)
#保存数据
df_output.to_csv('df_output.csv', index=False)
🍞四.核心逻辑
得到向量化的训练数据后,我们对应上面的Transformer结构图中左半部分的encoder编码器,我们构建了一个主体由encoder与softmax分类器组成的Transformer情感分类器
python
import torch
import torch.nn as nn
#参数配置
embed_dim =37411 # 字 Embedding 的维度
d_model=256
d_ff = 1024 # 前向传播隐藏层维度
d_k = d_v = 64 # K(=Q), V的维度
n_layers = 3 # 有多少个encoder
n_heads = 4 # Multi-Head Attention设置为8
seq_len=54
batch_size=64
src_vocab_size=37411
tgt_vocab_size=3
dropout=0.1
#te 和 pe 都是形状为 (batch_size, seq_len, embed_dim) 的张量
class PositionalEncoding(nn.Module): #nn.Module是一个用于构建神经网络模型的基类
def __init__(self):
super(PositionalEncoding, self).__init__()
self.d_model=d_model
self.seq_len=seq_len
self.dropout = nn.Dropout(p=dropout)
pos_table = np.array([
[pos / np.power(10000, 2 * i / d_model) for i in range(d_model)]
if pos != 0 else np.zeros(d_model) for pos in range(seq_len)])
pos_table[1:, 0::2] = np.sin(pos_table[1:, 0::2]) # 字嵌入维度为偶数时
pos_table[1:, 1::2] = np.cos(pos_table[1:, 1::2]) # 字嵌入维度为奇数时
self.pos_table = torch.LongTensor(pos_table).to(device)
def forward(self, enc_inputs): # enc_inputs: [batch_size, seq_len, d_model] 32*54*100
#enc_inputs = torch.from_numpy(enc_inputs).to(torch.int64) # 转换为LongTensor类型
enc_inputs += self.pos_table[:enc_inputs.size(1), :] #self.pos_table[:54, :]
return self.dropout(enc_inputs.to(device)) #[batch_size, seq_len, d_model] 32*54*100
def get_attn_pad_mask(seq_q, seq_k): # seq_q: [batch_size, seq_len] ,seq_k: [batch_size, seq_len] 32*54
batch_size, len_q = seq_q.size()
batch_size, len_k = seq_k.size()
pad_attn_mask = seq_k.data.eq(0).unsqueeze(1) # 判断 输入那些含有P(=0),用1标记 ,[batch_size, 1, len_k]
return pad_attn_mask.expand(batch_size, len_q, len_k) # 扩展成多维度
def get_attn_subsequence_mask(seq): # seq: [batch_size, tgt_len] 32*3
attn_shape = [seq.size(0), seq.size(1), seq.size(1)]
subsequence_mask = np.triu(np.ones(attn_shape), k=1) # 生成上三角矩阵,[batch_size, tgt_len, tgt_len]
subsequence_mask = torch.from_numpy(subsequence_mask).byte() # [batch_size, tgt_len, tgt_len]
return subsequence_mask #32*3*3
class ScaledDotProductAttention(nn.Module):
#给定一个查询向量Q、一个键向量K、一个值向量V和一个注意力掩码attn_mask,
#该模块会计算出每个查询向量对应的上下文向量context和注意力矩阵attn。
def __init__(self):
super(ScaledDotProductAttention, self).__init__()
def forward(self, Q, K, V, attn_mask): # Q: [batch_size, n_heads, len_q, d_k] 32*3*64 # attn_mask: [batch_size, n_heads, seq_len, seq_len]
scores = torch.matmul(Q, K.transpose(-1, -2)) / np.sqrt(d_k) # scores : [batch_size, n_heads, len_q, len_k]
scores.masked_fill_(attn_mask, -1e9) # 如果时停用词P就等于 0
attn = nn.Softmax(dim=-1)(scores)
context = torch.matmul(attn, V) # [batch_size, n_heads, len_q, d_v]
return context, attn
class MultiHeadAttention(nn.Module): #输入QKV和attn_mask四个矩阵
def __init__(self):
super(MultiHeadAttention, self).__init__()
# assert n_heads == d_model//d_k
# d_k = d_model//n_heads
self.W_Q = nn.Linear(d_model, d_k * n_heads, bias=False)
self.W_K = nn.Linear(d_model, d_k * n_heads, bias=False)
self.W_V = nn.Linear(d_model, d_v * n_heads, bias=False)
self.fc = nn.Linear(n_heads * d_v, d_model, bias=False)
def forward(self, input_Q, input_K, input_V, attn_mask): # input_Q: [batch_size, len_q, d_model]
# input_K: [batch_size, len_k, d_model]
# input_V: [batch_size, len_v(=len_k), d_model]
# attn_mask: [batch_size, seq_len, seq_len]
residual, batch_size = input_Q, input_Q.size(0)
Q = self.W_Q(input_Q).view(batch_size, -1, n_heads, d_k).transpose(1, 2) # Q: [batch_size, n_heads, len_q, d_k]
K = self.W_K(input_K).view(batch_size, -1, n_heads, d_k).transpose(1, 2) # K: [batch_size, n_heads, len_k, d_k]
V = self.W_V(input_V).view(batch_size, -1, n_heads, d_v).transpose(1, 2) # V: [batch_size, n_heads, len_v(=len_k), d_v]
attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1) # attn_mask : [batch_size, n_heads, seq_len, seq_len]
context, a
🫓总结
综上,我们基本了解了**"一项全新的技术啦"** :lollipop: ~~
恭喜你的内功又双叒叕得到了提高!!!
感谢你们的阅读:satisfied:
后续还会继续更新:heartbeat:,欢迎持续关注:pushpin:哟~
:dizzy:如果有错误❌,欢迎指正呀:dizzy:
:sparkles:如果觉得收获满满,可以点点赞👍支持一下哟~:sparkles:
【传知科技 -- 了解更多新知识】