电商评论文本情感分类(中文文本分类+中文词云图)(第一部分-textcnn)

电商评论文本情感分类(中文文本分类+中文词云图)

第一部分

第二部分Bert部分

本项目包含:

1.中文文本处理
2.中文词云图绘制
3.中文词嵌入
4.基于textcnn的中文文本分类(Test_Acc=89.2000)
5.基于bert的中文文本分类(Test_Acc=0.91)

其他说明

完整代码获取

👉点击下方链接跳转,可以在线运行点击这里跳转
因为代码很长,部分Class做了隐藏处理,右上角fork后可展示全部,感觉对你有用的话可以点个赞~
👉本项目分为Bert和TextCNN,通过点击左侧Notebook列表中的Notebook进行不同模型实现的查看
👉textcnn使用cpu即可,Bert使用的是T4-GPU,运行时间:10个epoch大概100分钟
👉部分代码复用了参考文章中的,但是总是会有一些问题,做了部分修改
👉参数没有精调,精调后应该还会有提升
👉代码实在是太长,CSDN分为两个文章发布,第一篇为textcnn,第二篇为bert,建议点击上方链接,fork后也可以把notebook下载下来

往期文章和专栏

👉往期文章可以关注我的专栏
下巴同学的数据加油小站
会不定期分享数据挖掘、机器学习、风控模型、深度学习、NLP等方向的学习项目,关注不一定能学到你想学的东西,但是可以学到我想学和正在学的东西😀

代码

导入相关包

import torch
from torch.utils.data import random_split
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import re
import numpy as np
import pandas as pd
import seaborn as sns
from pylab import rcParams
import matplotlib.pyplot as plt
from matplotlib import rc
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, classification_report
from collections import defaultdict
import jieba
%matplotlib inline

前期准备

# 固定随机种子
RANDOM_SEED = 2022
np.random.seed(RANDOM_SEED)
torch.manual_seed(RANDOM_SEED)device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
device

数据导入、清洗和检查

data = pd.read_csv('/home/mw/input/comment4cls4805/text2cls.csv')
data.drop('Unnamed: 0',axis=1,inplace=True)
data.head()

在这里插入图片描述

在这里插入图片描述

data['text_len']=data['text'].map(len)
data['text'].map(len).describe()

单击部分隐藏输出,双击全隐藏
count 10000.000000
mean 109.083700
std 126.177622
min 4.000000
25% 34.000000
50% 69.000000
75% 135.000000
max 1985.000000
Name: text, dtype: float64

文本清洗

#定义删除除字母,数字,汉字以外的所有符号的函数
def remove_pun(line):rule = re.compile(u"[^a-zA-Z0-9\u4E00-\u9FA5]")line = rule.sub('',line)return line
##读取停用词表
stopwords = [line.strip() for line in open('/home/mw/input/stop6931/中文停用词库.txt', 'r', encoding='gbk').readlines()]  
##字符清理
data['clean_text'] = data['text'].map(lambda x:remove_pun(x))
##结合停用词表进行分词
data['cut_text'] = data['clean_text'].apply(lambda x: " ".join(w for w in jieba.lcut(x) if w not in stopwords))

Building prefix dict from the default dictionary …
Dumping model to file cache /tmp/jieba.cache
Loading model cost 0.725 seconds.
Prefix dict has been built successfully.

词云图展示

from wordcloud import WordCloud,STOPWORDS,ImageColorGenerator
from PIL import Image
# background_image = np.array(Image.open("/home/mw/project/p.jpg"))
fig,ax = plt.subplots(figsize=(12,8), dpi=100)
mytext = ''
for i in range(len(data)):mytext += data['text'][i]
# wcloud = WordCloud(width=2400, height=1600,background_color="white",stopwords=stopwords,mask = background_image).generate(mytext)
wcloud = WordCloud(width=2400, height=1600,font_path=r'/home/mw/project/NotoSansHans-Black.otf').generate(mytext)
plt.imshow(wcloud)
plt.axis('off')

在这里插入图片描述

使用torchtext进行词嵌入

新学的,还不是很熟悉,研究了小半天才成功😀

import torchtext
from torchtext.data import Field,Example,Dataset
from torchtext import vocab

切分数据

df_train, df_test = train_test_split(data, test_size=0.2, random_state=RANDOM_SEED)
df_val, df_test = train_test_split(df_test, test_size=0.5, random_state=RANDOM_SEED)
df_train.shape, df_val.shape, df_test.shape

((8000, 5), (1000, 5), (1000, 5))

class DataFrameDataset(torchtext.data.Dataset):def __init__(self, df, fields, is_test=True, **kwargs):examples = []for i, row in df.iterrows():# label = row.label if not is_test else Nonelabel = row.label text = row.cut_textexamples.append(torchtext.data.Example.fromlist([text, label], fields))super().__init__(examples, fields, **kwargs)@staticmethoddef sort_key(ex):return len(ex.text)@classmethoddef splits(cls, fields, train_df, val_df=None, test_df=None, **kwargs):train_data, val_data, test_data = (None, None, None)data_field = fieldsif train_df is not None:train_data = cls(train_df.copy(), data_field, **kwargs)if val_df is not None:val_data = cls(val_df.copy(), data_field, **kwargs)if test_df is not None:test_data = cls(test_df.copy(), data_field, True, **kwargs)return tuple(d for d in (train_data, val_data, test_data) if d is not None)
TEXT = Field(sequential=True, lower=True, fix_length=500,tokenize=str.split,batch_first=True)
LABEL = Field(sequential=False, use_vocab=False)
fields = [('text',TEXT), ('label',LABEL)]train_ds, val_ds , test_ds= DataFrameDataset.splits(fields, train_df=df_train, val_df=df_val, test_df=df_test)
# 看一个随机的例子
print(vars(train_ds[666]))# 检查类型
print(type(train_ds[666]))

{‘text’: [‘酒店’, ‘位置’, ‘没得说’, ‘静安寺’, ‘边上’, ‘房间’, ‘里’, ‘一股’, ‘子’, ‘怪味’, ‘窗上’, ‘纱窗’, ‘没有’, ‘一夜’, ‘下来’, ‘两臂’, ‘苍蝇’, ‘蚊子’, ‘叮出’, ‘大包’, ‘点着’, ‘蚊香’, ‘国营企业’, ‘德行’], ‘label’: 0}
<class ‘torchtext.data.example.Example’>

MAX_VOCAB_SIZE = 25000
pretrained_name = 'sgns.sogou.word' # 预训练词向量文件名
pretrained_path = '/home/mw/input/s_w2v2564/' #预训练词向量存放路径
vectors = torchtext.vocab.Vectors(name=pretrained_name, cache=pretrained_path)TEXT.build_vocab(train_ds, max_size = MAX_VOCAB_SIZE, vectors = vectors,unk_init = torch.Tensor.zero_)

0%| | 0/364990 [00:00<?, ?it/s]Skipping token b’364990’ with 1-dimensional vector [b’300’]; likely a header
100%|██████████| 364990/364990 [00:37<00:00, 9853.04it/s]

LABEL.build_vocab(train_ds)
BATCH_SIZE = 128train_iterator, valid_iterator, test_iterator = torchtext.data.BucketIterator.splits((train_ds, val_ds, test_ds), batch_size = BATCH_SIZE,# sort_within_batch = False,device = device)

模型构建、编译、训练

class TextCNN(nn.Module):def __init__(self, class_num, # 最后输出的种类数 filter_sizes, # 卷积核的长也就是滑动窗口的长 filter_num,   # 卷积核的数量 vocabulary_size, # 词表的大小embedding_dimension, # 词向量的维度vectors, # 词向量dropout): # dropout率super(TextCNN, self).__init__() # 继承nn.Modulechanel_num = 1  # 通道数,也就是一篇文章一个样本只相当于一个feature mapself.embedding = nn.Embedding(vocabulary_size, embedding_dimension) # 嵌入层 self.embedding = self.embedding.from_pretrained(vectors) #嵌入层加载预训练词向量self.convs = nn.ModuleList([nn.Conv2d(chanel_num, filter_num, (fsz, embedding_dimension)) for fsz in filter_sizes])  # 卷积层self.dropout = nn.Dropout(dropout) # dropoutself.fc = nn.Linear(len(filter_sizes) * filter_num, class_num) #全连接层def forward(self, x):# x维度[句子长度,一个batch中所包含的样本数] 例:[3451,128]x = self.embedding(x) # #经过嵌入层之后x的维度,[句子长度,一个batch中所包含的样本数,词向量维度] 例:[3451,128,300]# print('1',x.shape)# x = x.permute(1,0,2) # permute函数将样本数和句子长度换一下位置,[一个batch中所包含的样本数,句子长度,词向量维度] 例:[128,3451,300]# print('2',x.shape)x = x.unsqueeze(1) # # conv2d需要输入的是一个四维数据,所以新增一维feature map数 unsqueeze(1)表示在第一维处新增一维,[一个batch中所包含的样本数,一个样本中的feature map数,句子长度,词向量维度] 例:[128,1,3451,300]x = [conv(x) for conv in self.convs] # 与卷积核进行卷积,输出是[一个batch中所包含的样本数,卷积核数,句子长度-卷积核size+1,1]维数据,因为有[3,4,5]三张size类型的卷积核所以用列表表达式 例:[[128,16,3459,1],[128,16,3458,1],[128,16,3457,1]]x = [sub_x.squeeze(3) for sub_x in x]#squeeze(3)判断第三维是否是1,如果是则压缩,如不是则保持原样 例:[[128,16,3459],[128,16,3458],[128,16,3457]]x = [F.relu(sub_x) for sub_x in x] # ReLU激活函数激活,不改变x维度 x = [F.max_pool1d(sub_x,sub_x.size(2)) for sub_x in x] # 池化层,根据之前说的原理,max_pool1d要取出每一个滑动窗口生成的矩阵的最大值,因此在第二维上取最大值 例:[[128,16,1],[128,16,1],[128,16,1]]x = [sub_x.squeeze(2) for sub_x in x] # 判断第二维是否为1,若是则压缩 例:[[128,16],[128,16],[128,16]]x = torch.cat(x, 1) # 进行拼接,例:[128,48]x = self.dropout(x) # 去除掉一些神经元防止过拟合,注意dropout之后x的维度依旧是[128,48],并不是说我dropout的概率是0.5,去除了一半的神经元维度就变成了[128,24],而是把x中的一些神经元的数据根据概率全部变成了0,维度依旧是[128,48]logits = self.fc(x) # 全接连层 例:输入x是[128,48] 输出logits是[128,10]return logits
class_num = len(LABEL.vocab)-1 # 类别数目,会生成一个开头占位
filter_size = [2,3,4]  # 卷积核种类数 
filter_num=64   # 卷积核数量
vocab_size = len(TEXT.vocab) # 词表大小
embedding_dim = TEXT.vocab.vectors.size()[-1] # 词向量维度
vectors = TEXT.vocab.vectors # 词向量
dropout=0.5 
learning_rate = 0.001  # 学习率
epochs = 5   # 迭代次数
save_dir = '/home/mw/project/' # 模型保存路径
steps_show = 20   # 每20步查看一次训练集loss和mini batch里的准确率
steps_eval = 100  # 每100步测试一下验证集的准确率
early_stopping = 1000  # 若发现当前验证集的准确率在1000步训练之后不再提高 一直小于best_acc,则提前停止训练textcnn_model = TextCNN(class_num=class_num,filter_sizes=filter_size,filter_num=filter_num,vocabulary_size=vocab_size,embedding_dimension=embedding_dim,vectors=vectors,dropout=dropout)
def train(train_iter, dev_iter, model):if torch.cuda.is_available(): # 判断是否有GPUmodel.cuda()optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate) # 梯度下降优化器,采用Adamsteps = 0best_acc = 0last_step = 0model.train()for epoch in range(1, epochs + 1): for batch in train_iter:feature, target = batch.text, batch.labelif torch.cuda.is_available(): # 如果有GPU将特征更新放在GPU上feature,target = feature.cuda(),target.cuda() optimizer.zero_grad() # 将梯度初始化为0,每个batch都是独立训练地,因为每训练一个batch都需要将梯度归零logits = model(feature)loss = F.cross_entropy(logits, target) # 计算损失函数 采用交叉熵损失函数loss.backward()  # 反向传播optimizer.step() # 放在loss.backward()后进行参数的更新steps += 1 if steps % steps_show == 0: # 每训练多少步计算一次准确率,我这边是1,可以自己修改corrects = (torch.max(logits, 1)[1].view(target.size()).data == target.data).sum() # logits是[128,10],torch.max(logits, 1)也就是选出第一维中概率最大的值,输出为[128,1],torch.max(logits, 1)[1]相当于把每一个样本的预测输出取出来,然后通过view(target.size())平铺成和target一样的size (128,),然后把与target中相同的求和,统计预测正确的数量train_acc = 100.0 * corrects / batch.batch_size # 计算每个mini batch中的准确率print('steps:{} - loss: {:.6f}  acc:{:.4f}'.format(steps,loss.item(),train_acc))if steps % steps_eval == 0: # 每训练100步进行一次验证dev_acc = dev_eval(dev_iter,model)if dev_acc > best_acc:best_acc = dev_acclast_step = stepsprint('Saving best model, acc: {:.4f}%\n'.format(best_acc))save(model,save_dir, steps)else:if steps - last_step >= early_stopping:print('\n提前停止于 {} steps, acc: {:.4f}%'.format(last_step, best_acc))raise KeyboardInterrupt
def dev_eval(dev_iter,model):model.eval()corrects, avg_loss = 0, 0for batch in dev_iter:feature, target = batch.text, batch.labelif torch.cuda.is_available():feature, target = feature.cuda(), target.cuda()logits = model(feature)loss = F.cross_entropy(logits, target)avg_loss += loss.item()corrects += (torch.max(logits, 1)[1].view(target.size()).data == target.data).sum()size = len(dev_iter.dataset)avg_loss /= sizeaccuracy = 100.0 * corrects / sizeprint('\nEvaluation - loss: {:.6f}  acc: {:.4f}%({}/{}) \n'.format(avg_loss,accuracy,corrects,size))return accuracy
# 定义模型保存函数
import os
def save(model, save_dir, steps):if not os.path.isdir(save_dir):os.makedirs(save_dir)save_path = 'bestmodel_steps{}.pt'.format(steps)save_bestmodel_path = os.path.join(save_dir, save_path)torch.save(model.state_dict(), save_bestmodel_path)
train(train_iterator,valid_iterator,textcnn_model)

steps:20 - loss: 0.529808 acc:80.4688
steps:40 - loss: 0.416786 acc:79.6875
steps:60 - loss: 0.314106 acc:89.0625
steps:80 - loss: 0.289211 acc:89.8438
steps:100 - loss: 0.296632 acc:89.8438

Evaluation - loss: 0.002373 acc: 88.3000%(883/1000)

Saving best model, acc: 88.3000%

steps:120 - loss: 0.226822 acc:89.8438
steps:140 - loss: 0.208629 acc:90.6250
steps:160 - loss: 0.213932 acc:92.1875
steps:180 - loss: 0.329256 acc:88.2812
steps:200 - loss: 0.153643 acc:96.8750

Evaluation - loss: 0.002091 acc: 90.5000%(905/1000)

Saving best model, acc: 90.5000%

steps:220 - loss: 0.200416 acc:92.9688
steps:240 - loss: 0.171485 acc:92.1875
steps:260 - loss: 0.155608 acc:95.3125
steps:280 - loss: 0.109367 acc:96.0938
steps:300 - loss: 0.098254 acc:97.6562

Evaluation - loss: 0.002013 acc: 90.4000%(904/1000)

查看测试集效果

dev_eval(test_iterator,textcnn_model)

Evaluation - loss: 0.002039 acc: 89.2000%(892/1000)

tensor(89.2000)

参考文章

1.Huggingface Transformers实战教程
2.kaggle-pytorch-text-classification-torchtext-lstm
3.中文文本分类
4.中文词向量
5.torchtext-Field详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/50743.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详解升讯威在线客服系统前端多国语言实现技术:原生支持葡文、印尼文、土耳其文、俄文

我在业余时间开发维护了一款免费开源的升讯威在线客服系统&#xff0c;也收获了许多用户。对我来说&#xff0c;只要能获得用户的认可&#xff0c;就是我最大的动力。 越来越多的用户向我提出需求&#xff0c;希望为访客端增加更多的界面语言&#xff0c;如葡文、印尼文、土耳其…

【NLP论文翻译】基于显著性感知主题建模的面向主题的客户服务口语对话摘要

本博客为博主论文阅读记录&#xff0c;原论文和github地址如下&#xff1a; 原论文下载&#xff1a;https://ojs.aaai.org/index.php/AAAI/article/view/17723/17530 代码&#xff1a;https://github.com/RowitZou/topic-dialog-summ 本篇博客所介绍论文为AAAI 2021论文 NLP领域…

ChatExcel 来了,太酷炫了!

点击上方“Java基基”&#xff0c;选择“设为星标” 做积极的人&#xff0c;而不是积极废人&#xff01; 每天 14:00 更新文章&#xff0c;每天掉亿点点头发... 源码精品专栏 原创 | Java 2021 超神之路&#xff0c;很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应…

本周最火AutoGPT!GitHub3.6万+标星,解决复杂任务全程无需人类插手

金磊 丰色 发自 凹非寺量子位 | 公众号 QbitAI 本周AI圈”最红炸子鸡“诞生——AutoGPT。 不仅如此&#xff0c;这款软件系统的横空出世&#xff0c;一举将AI进程推向了新高度—— 自主人工智能。 顾名思义&#xff0c;它所具备的能力主打的就是一个“自主”&#xff0c;完全不…

智慧园区如何搭乘数字孪生这列快车?

无论是2022年的火爆的元宇宙还是今年出圈的ChatGPT&#xff0c;都体现着数字技术嵌入社会生活是大趋势&#xff0c;数字孪生作为智能技术的一大亮点&#xff0c;它在智慧园区中的应用会是怎样呢&#xff1f;今天我们就来聊一聊&#xff01; &#xff08;全文3000字&#xff0c;…

超详细!简单的物联网模块esp8266接入小爱爱同学控制电器(一)——控制开关灯

简单的物联网模块esp8266接入小爱爱同学控制电器&#xff08;一&#xff09;——控制开关灯 闲来无事&#xff0c;分享一个简单使用的物联网实现系统&#xff08;小爱同学esp8266&#xff09;&#xff0c;实现手机控制开关灯小爱同学控制开关灯 1.准备材料&#xff1a; 1.1 …

想用天猫精灵、小爱同学语音控制灯、窗帘、卷帘门、投影幕……实现你的智能家居梦?用十几元钱制作一个红外射频转发器试试吧

使用场景 自从智能音箱出现以后&#xff0c;是不是想用智能音箱控制家里的电器和设备&#xff1f;但是购买接入天猫精灵和小爱同学的设备都很贵&#xff0c;比如一套普通的电动窗帘架300元左右&#xff0c;但是接入智能音箱控制的最少要700元。想尝试智能家居控制的老铁们肯定…

程序员的真实工资是多少?

众所周知&#xff0c;程序员这个圈子工资差异还是蛮大的&#xff0c;很多猿猿在一线城市少的拿8k&#xff0c;多的10k也有&#xff0c;都是凭自己的能力赚钱。 今年受疫情影响&#xff0c;不少企业开始缩减招聘名额&#xff0c;更别说涨薪了&#xff01;据统计&#xff0c;今年…

腾讯开发微信花了多少钱?程序员告诉你有多劳民伤财

微信这玩意&#xff0c;你就是照着已有的产品开发个一摸一样的&#xff0c;最少1000万。 各种功能都是程序员一行行写出来的&#xff0c;绕不过去。你觉得它简单&#xff0c;因为它设计得比较易用&#xff0c;用户觉不出它无数的功能点。 40个人的团队&#xff0c;人均薪资两万…

程序员的工资高,到底程序员的工资有多高?你不了解的程序员!

都说程序员的工资高&#xff0c;程序员工资是不是过高&#xff1f; 如果和国内和其他职业横向比较&#xff0c;是的&#xff0c;局部过高。全世界和其他程序员&#xff08;主要是美帝&#xff09;比&#xff0c;不是&#xff0c;太低。互联网是不是毒瘤&#xff1f;以后优秀的…

软件测试【月入1万如讨饭】需要多少才能算高薪呢?

小编热衷于收集整理资源&#xff0c;记录踩坑到爬坑的过程。希望能把自己所学&#xff0c;实际工作中使用的技术、学习方法、心得及踩过的一些坑&#xff0c;记录下来。也希望想做软件测试的你一样&#xff0c;通过我的分享可以少走一些弯路&#xff0c;可以形成一套自己的方法…

程序员分哪几种,分别薪资是多少

这是本文的目录 前言程序员的类别程序员的薪资一般是多少这里着重介绍一下python程序员&#xff1a;python副业介绍1、兼职处理数据2、兼职查询资料3、兼职P图 零基础Python学习资料介绍附上Python学习指南&#x1f449;Python学习路线汇总&#x1f448;&#x1f449;Python必备…

Google程序员究竟能挣多少钱?

美国知乎Quora上出了一篇名为“How Much Does Google Engineer Make&#xff1f;”的问题。 其中&#xff0c;一位匿名回答者答道&#xff0c;虽然自己在Google是个经验“尚浅”的软件工程师&#xff0c;但自己的年薪已经达到30万美金了&#xff01; 接着&#xff0c;在知乎上也…

程序员在一线城市立足,需要月入多少钱?

自从3 月 21 日&#xff0c;北京市人才落户新政“《北京市引进人才管理办法&#xff08;试行&#xff09;》”发布后&#xff0c;社会上热烈讨论&#xff0c;小编注意到网上有很多程序员盆友对此政策十分关注&#xff01; 第五条 加大科技创新人才及科技创新服务人才引进力度&…

读了几篇boosting文献的收获。。。

距离上一篇blog都3个多月了。最近也是有的懒&#xff0c;看到别人的blog层次都很高&#xff0c;总是介绍些opencv的粗浅的东西&#xff0c;总是感觉自惭形愧。所以一直野就没写啥。白驹过隙&#xff0c;3个月&#xff0c;我都转博了&#xff0c;opencv都2.4.3了&#xff0c;可是…

百度语音DuerOS对接

百度语音DuerOS对接 百度语音对接一 流程二 编码三 使用规则 百度语音对接 百度语音对接是基于云云对接的方式实现&#xff0c;主要有四个重点 1 账号授权 2 webService-发现设备 3 webService-控制设备 4 webService-属性上报 本篇值描述1和大致的流程、实际的文档可以参考官…

回顾 | 阿里数据中台建模

阿里中台的概念&#xff0c;可以说是近些年来的颇为火爆的概念。从十余年前的阿里在内部完成这一过程&#xff0c;并提出了“中台”概念&#xff1b;到后面中台概念逐步被外部接受并在2019年爆火兴起。数据中台爆火背后&#xff0c;既有传统企业转型焦虑的市场东风&#xff0c;…

ChatGPT 成功的原因:把模型当产品做

当 ChatGPT 成为风靡全球的科技热点&#xff0c;用 60 多天的时间实现了用户破亿的成绩&#xff0c;不禁让人思索&#xff1a;它为什么能成功&#xff1f; 对此&#xff0c;IDEA 研究院讲席科学家张家兴阐述了自己的观点。 《2022-2023 中国开发者大调查》重磅启动&#xff0c;…

模型越大,AI编程个性化就越难?

分享嘉宾 | 郝逸洋&#xff0c;李钟麒 整理 | 朱珂欣 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 近些年来&#xff0c;随着AI等前沿技术的推陈出新及各场景中的应用&#xff0c;让更多的人触达AI时代的科技前沿。AI编程作为AI的子领域&#xff0c;如今也离普…

男子与AI聊天后自杀,Chat GPT被紧急叫停,AI有了思想会怎样?

AI如果有了思想&#xff0c;会发生什么&#xff1f; 前一阵&#xff0c;一位热衷环境问题的比利时男子&#xff0c;和人工智能“伊丽莎”聊天6周后&#xff0c;突然选择自杀身亡。 在他们大量聊天记录里&#xff0c;人们竟然发现“伊丽莎”对该男子说&#xff1a;“我们将永远…