|
|
<template>
|
|
|
<div class="main-top">
|
|
|
<span style="font-weight: bold; font-size: 18px; color: #3596eb">任务描述</span>
|
|
|
<p style="font-weight: 400; font-size: 12px; color: #e2e7ee !important">使用“用户商品评论数据”,进行各类文本挖掘任务,分析评论情感情绪,最后提交实训任务。</p>
|
|
|
</div>
|
|
|
<div class="main-but">
|
|
|
<el-button @click="knowledgeImport">
|
|
|
<img src="../../../assets/images/导入.png" alt="" />
|
|
|
算法知识导入
|
|
|
</el-button>
|
|
|
<el-button @click="taskSubmit">
|
|
|
<img src="../../../assets/images/提交.png" alt="" />
|
|
|
实训任务提交
|
|
|
</el-button>
|
|
|
</div>
|
|
|
<div class="app-main">
|
|
|
<div class="main-left">
|
|
|
<div>
|
|
|
<div class="left-top">
|
|
|
<span>一、选择指标</span>
|
|
|
</div>
|
|
|
<div class="metrics">
|
|
|
<el-select v-model="n_dataTableQuery.tableName" placeholder="请选择数据" style="width: 180px" @change="selectType">
|
|
|
<el-option v-for="item in algorithmStore.userDataLabel2" :key="item" :label="item" :value="item" />
|
|
|
</el-select>
|
|
|
</div>
|
|
|
<div class="metrics-table" style="margin-top: 10px">
|
|
|
<el-table border ref="multipleTableRef" :data="algorithmStore.indexLibrary" style="width: 100%" :header-cell-style="headerCellStyle" @selection-change="handleSelectionChange">
|
|
|
<el-table-column type="selection" prop="date" label="Date" align="center" />
|
|
|
<el-table-column label="指标" align="center" prop="label" />
|
|
|
</el-table>
|
|
|
<el-button @click="submit">确定</el-button>
|
|
|
</div>
|
|
|
<div class="left-top">
|
|
|
<span>二、数据预处理</span>
|
|
|
</div>
|
|
|
<div class="metrics">
|
|
|
<el-select v-model="preProcessText.text1" placeholder="请选择数据" style="width: 180px">
|
|
|
<el-option label="数据去重" value="数据去重" />
|
|
|
</el-select>
|
|
|
</div>
|
|
|
<div class="metrics" style="margin-top: 15px">
|
|
|
<el-select v-model="preProcessText.text2" placeholder="请选择数据" style="width: 180px" @change="preProcess">
|
|
|
<el-option label="缺失值处理-剔除数据" value="缺失值处理-剔除数据" />
|
|
|
<el-option label="缺失值处理一均值替代" value="缺失值处理一均值替代" />
|
|
|
</el-select>
|
|
|
</div>
|
|
|
<div class="left-top" style="margin-top: 10px">
|
|
|
<span>三、模型参数设置</span>
|
|
|
</div>
|
|
|
<div class="metrics-table" style="margin-top: 10px">
|
|
|
<el-select v-model="preProcessText.text3" placeholder="请选择模型" style="width: 180px" @change="switchingModels">
|
|
|
<el-option label="评论观点抽取" value="评论观点抽取" />
|
|
|
<el-option label="情感倾向分析" value="情感倾向分析" />
|
|
|
<el-option label="词频分析" value="词频分析" />
|
|
|
<el-option label="词云生成" value="词云生成" />
|
|
|
</el-select>
|
|
|
<el-button @click="sentimentAnalysis">情感分析</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div style="margin-top: auto">
|
|
|
<div class="startOver" @click="restart">
|
|
|
<img src="../../../assets/images/重新开始.png" alt="" />
|
|
|
<span>重新开始</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="main-right" v-loading="loading" :element-loading-spinner="svg" element-loading-svg-view-box="-10, -10, 50, 50" element-loading-background="rgba(122, 122, 122, 0.8)">
|
|
|
<el-scrollbar height="550px" style="padding: 20px">
|
|
|
<div>
|
|
|
<span style="font-weight: bold; font-size: 18px; color: #3596eb">分析数据:</span>
|
|
|
|
|
|
<div class="analysis">
|
|
|
<el-input v-model="g_analyzeTheNumberOfDataItems" :rows="2" type="textarea" placeholder="" />
|
|
|
</div>
|
|
|
<div class="footer">
|
|
|
<el-button type="primary" plain @click="sentimentAnalysis">情感分析</el-button>
|
|
|
<el-button type="primary" plain @click="clear">清空</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="analysisResults" style="margin-top: 20px">
|
|
|
<span style="font-weight: bold; font-size: 18px; color: #3596eb; display: block">分析结果:</span>
|
|
|
<el-button v-if="preProcessText.text3 !== '词云生成'" @click="download">下载</el-button>
|
|
|
<div v-show="preProcessText.text3 == '词频分析'" v-loading="loading2" :element-loading-spinner="svg" element-loading-svg-view-box="-10, -10, 50, 50" element-loading-background="rgba(122, 122, 122, 0.8)">
|
|
|
<h4>词频列表</h4>
|
|
|
<p>你可以对词频词语进行修改删除以及增加,频次数越大,显示越大,最多200关键词。</p>
|
|
|
<div class="conditions">
|
|
|
<label>显示条件:</label>
|
|
|
<div>
|
|
|
<span>频次>=</span>
|
|
|
<el-input v-model="mWordFrequency.frequency" style="width: 120px" placeholder="" v-debounce="wordFrequencySearch" />
|
|
|
</div>
|
|
|
<div>
|
|
|
<span>词语长度>=</span>
|
|
|
<el-input v-model="mWordFrequency.namenum" style="width: 120px" placeholder="" v-debounce="wordFrequencySearch" />
|
|
|
</div>
|
|
|
</div>
|
|
|
<el-table border :data="aWordFrequency" style="width: 80%" :header-cell-style="headerCellStyle">
|
|
|
<el-table-column :label="`内容(${aWordFrequency?.length || 0})`" align="center" prop="keyword" width="747" />
|
|
|
<el-table-column label="频次" align="center" prop="frequency" />
|
|
|
</el-table>
|
|
|
<pagination :autoScroll="false" v-show="nOriginalWordFrequencyNum > 0" :total="nOriginalWordFrequencyNum" v-model:page="mWordFrequency.index" v-model:limit="mWordFrequency.size" @pagination="wordFrequencyNext" />
|
|
|
</div>
|
|
|
<!-- v-show="preProcessText.text3 == '词云生成'" -->
|
|
|
<div class="div" v-if="preProcessText.text3 == '词云生成'">
|
|
|
<div id="mywordcloud_positive" style="width: 50%; height: 600px"></div>
|
|
|
</div>
|
|
|
<div ref="captureElement" v-if="preProcessText.text3 == '情感倾向分析' && n_emotiveTendency == 0 ? true : n_emotiveTendency">
|
|
|
<div class="emotion_analysis">
|
|
|
<div class="emotion_analysis-ani-in-le">
|
|
|
<span>正向情感</span>
|
|
|
<img src="../../../assets/images/笑脸.png" alt="" />
|
|
|
</div>
|
|
|
|
|
|
<el-progress style="width: 480px" :text-inside="true" :stroke-width="20" :percentage="n_emotiveTendency" :color="'#ff540a'" />
|
|
|
<div class="emotion_analysis-ani-in-ri">
|
|
|
<img src="../../../assets/images/哭脸.png" alt="" />
|
|
|
<span>负向情感</span>
|
|
|
</div>
|
|
|
<div class="emotion_analysis_title">{{ g_emotionAnalysis == 0 ? "情感偏负向" : g_emotionAnalysis == 1 ? "情感偏中性" : "情感偏正向" }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div ref="captureElement" class="comment-opinion" v-if="preProcessText.text3 == '评论观点抽取'">
|
|
|
<div class="result" v-for="(item, sentiment) in aCommentOpinion" :key="item.abstract">
|
|
|
<div class="result-pos" :class="sentiment == 0 ? 'result-neg' : sentiment == 1 ? 'result-neutral' : 'result-img'"></div>
|
|
|
<div class="result-title">{{ sentiment == 0 ? "负向评价" : sentiment == 1 ? "中性评价" : "正向评价" }}:</div>
|
|
|
<div class="result-view" v-for="items in item" :key="items">
|
|
|
<span v-if="items.opinions.length == 0">{{ items.aspect }}</span>
|
|
|
<span v-for="i in items.opinions" :key="i">
|
|
|
{{ items.aspect + i }}
|
|
|
<div class="result-bar"></div>
|
|
|
</span>
|
|
|
<div class="result-bar"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-scrollbar>
|
|
|
</div>
|
|
|
</div>
|
|
|
<el-dialog v-model="dialogVisible" title="实训任务提交" width="700" custom-class="dialogClass" :show-close="false">
|
|
|
<template #header="{ close, titleId, titleClass }">
|
|
|
<div class="my-header">
|
|
|
<span :id="titleId" :class="titleClass">实训任务提交</span>
|
|
|
<img class="gbImg" src="../../../assets/images/关闭.png" @click="close" alt="" />
|
|
|
</div>
|
|
|
<img class="xtImg" src="../../../assets/images/线条.png" alt="" />
|
|
|
</template>
|
|
|
<div class="from-item">
|
|
|
<el-form :inline="true" label-width="84px" :model="form" class="demo-form-inline">
|
|
|
<el-form-item label="词频分析:" label-width="120px">
|
|
|
<el-form-item label="最多数量词语:" label-width="120px" label-position="left">
|
|
|
<el-input style="width: 140px" v-model="formInline.max" clearable />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="频次:" label-position="left" label-width="46px">
|
|
|
<el-input style="width: 140px" v-model="formInline.frequency" clearable />
|
|
|
</el-form-item>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="评论观点抽取: " label-width="120px">
|
|
|
<el-form-item label="前两位词语:" label-width="120px" label-position="left">
|
|
|
<el-input style="width: 140px" v-model="formInline.text1" clearable />
|
|
|
</el-form-item>
|
|
|
<el-form-item>
|
|
|
<el-input style="width: 140px" v-model="formInline.text2" clearable />
|
|
|
</el-form-item>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="情感倾向分析: " label-width="120px">
|
|
|
<el-radio-group v-model="formInline.emotional" style="margin-left: 31px" fill="red" text-color="#ffffff">
|
|
|
<el-radio value="1" label="正向" size="large"></el-radio>
|
|
|
<el-radio value="2" label="负向" size="large"></el-radio>
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="词云生成: " label-width="120px">
|
|
|
<el-form-item label="中间最突出单词: " label-width="120px">
|
|
|
<el-input style="width: 140px" v-model="formInline.prominent" clearable />
|
|
|
</el-form-item>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
</div>
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
|
<el-button @click="submitTask">确定</el-button>
|
|
|
<el-button type="primary" @click="dialogVisible = false">返回</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
<popModel :showModel="b_algorithmKnowledge" title="情感分析" @closePop="b_algorithmKnowledge = false">
|
|
|
<template v-slot:content>
|
|
|
<!-- 情感分析 -->
|
|
|
<div style="color: #000000 !important">
|
|
|
<p style="font-weight: 700; font-size: 22px">总览</p>
|
|
|
<p>
|
|
|
文本分析指对文本数据进行表示 (representation),处理(processing)和建模(modeling)来获得有用的见解(insight)。
|
|
|
<br />
|
|
|
文本分析的一个挑战是高维度(high dimensionality)。
|
|
|
<br />
|
|
|
<br />
|
|
|
语料库: 指在自然语言处理中用于各种目的的大量文本(文档)集合。
|
|
|
<br />
|
|
|
<br />
|
|
|
另一个挑战是: 大多数情况下文本是非结构化数据(not structured)。
|
|
|
</p>
|
|
|
<img src="@/assets/images/f4001.png" alt="" style="width: 600px;"/>
|
|
|
<p style="font-weight: 700; font-size: 22px">文本分析步骤</p>
|
|
|
<p>
|
|
|
1.句法分析(Parsing): 是指处理非结构化文本使其具有一定的结构,供将来分析的过程。句法分析将文本进行解构,然后以一种更为结构化的方式来呈现。(unsturctured -> sturctured)
|
|
|
<br />
|
|
|
2.搜索和检索 (Search and retrieval): 在一个语料库中识别包含例如特殊单词,短语,主题或者实体(如人或组织)等搜索项(search items)的文档。这些搜索项通常称为关键术语 (key term)。
|
|
|
<br />
|
|
|
3.文本挖掘 (text mining): 指使用前两步产生的术语和索引来发现与感兴趣领域或问题相关的有意义的见解(discover meaningful insights)。
|
|
|
<br />
|
|
|
聚类(clustering) 和 分类 (classification) 都可以应用于文本挖掘。比如:将文档聚化成簇 或者 分类文本以进行情感分析(sentiment analysis)。
|
|
|
<br />
|
|
|
文本挖掘可以利用来自各个研究领域的方法和技术,如统计分析(statistical analysis),信息检索(information retrieval),数据挖掘(data mining)和自然语言处理(natural language processing)等。
|
|
|
</p>
|
|
|
<p style="font-weight: 700; font-size: 22px">挑战</p>
|
|
|
<li>语法(syntax): 涉及句子结构和语法规则。</li>
|
|
|
<li>语义(semantics): 是对句子含义的研究。</li>
|
|
|
<li>语用(pragmatic): 关注特定上下文中句子的含义。</li>
|
|
|
<li>同义词(homonyms): 具有相同拼写但含义不同的单词。</li>
|
|
|
<li>缩写词(acronyms): 是单词的缩写形式。</li>
|
|
|
<p>
|
|
|
对于计算机来说,文本只是编码和数字的字符序列(sequence of characters)。它不了解语法,语义(semantic meanings)和其实用含义(pragmatic meaning)。
|
|
|
<br />
|
|
|
同时,在原始形式中,单词或者文本之间没有自然相似性度量(nature similarity metric)。(因此难以执行聚类或分类)
|
|
|
<br />
|
|
|
因此,我们需要以适合用于聚类和分类的形式来表示和处理文本。
|
|
|
</p>
|
|
|
<p style="font-weight: 700; font-size: 22px">第一步:收集原始文本数据(Collecting Raw Text)</p>
|
|
|
<p>对于文本分析来说,进行任何事情之前必须先收集数据。</p>
|
|
|
<li>可以在许多网站上对用户生成的内容(user-generated contents)开始积极的监控。(可以使用公开的API, 网页爬虫 web scraper/crawler)</li>
|
|
|
<li>准备处理非结构化或者半结构化数据。</li>
|
|
|
<p style="font-weight: 700; font-size: 22px">第二步:表示文本 (Representing Text)</p>
|
|
|
<p>使用文本规范化的技术(text normalization techniques)对原始文本进行转化。</p>
|
|
|
<li>分词 (tokenization): 指从文本正文中分离(也叫做标记)单词 (seperating words)。</li>
|
|
|
<p>
|
|
|
1.按空格(spaces)来分词。(可能会把标点符号也给分出来,比如 day. )
|
|
|
<br />
|
|
|
2.基于标点符号和空格来分词 (可能会把 we’ll, can’t, state-of-the-art 等直接分开)
|
|
|
<br />
|
|
|
分词比想象中更困难 (résumé 和 resume)
|
|
|
<br />
|
|
|
没有一个分词器适用于每一种情形(no one-size-fits-all)。
|
|
|
</p>
|
|
|
<li>大小写转换 (case folding): 将所有的字母都变成小写(或者都变成大写)。</li>
|
|
|
<p>
|
|
|
1.但是在一些情况下可能会出错: 如专有名词,缩写,公司名等(General Motors; WHO; US;)
|
|
|
<br />
|
|
|
2.减少错误的方法是对不进行大小写转换的单词建立一个查找表(lookup table)。
|
|
|
</p>
|
|
|
<li>停止词(stop word): 在给定语言中,并非所有的单词都需要被考虑。(比如:the, a, of, and, to等这些不太可能有助于语义的理解)</li>
|
|
|
<li>词根法(Lemmatization)和词干法(Stemming): 词根法看单词的意义(如: Goose, geese, goose, gander, ganders)。词干法看单词的组成(如:walk, walking, Walk, walks, walked)。最受欢迎的是"Porter stemmer", “WordNet”。</li>
|
|
|
<li>词袋法(Bag-of-words representation): 将文档转化成高维向量(high-dimensional vector),向量指示了文档中各个单词的 存在/不存在/出现频率(presence/absence/frequency)。</li>
|
|
|
<img src="@/assets/images/f4002.png" alt="" style="width: 600px;height: 500px;"/>
|
|
|
<p>词袋法足够简单(朴素且过分简化问题 naive and over-simplified) 并被广泛应用于文本分析问题中 (是入门的好方法)。其将文档表示为一组词语(单词),而忽略了其他信息(如顺序 order,上下文 context,推论 inferences和语义 semantics)。比如,“a dog bites a man” 和 "a man bites a dog"意思完全不同但是他们在词袋法里是同一种表示。</p>
|
|
|
<li>语料库的表示(Representation of a corpus): 语料库可以大到包括一种或者多种语言的所有文档,也可以小到仅限于特定领域(focused on a specific domains)。</li>
|
|
|
<p>
|
|
|
大多数语料库都具有元数据,比如语料库的大小以及文本提取的领域。某些语料库包含了文本中出现的每个词的信息内容(Information content, IC)。信息内容是用来表示语料库中词语重要性(importance of a term)的度量。IC值较高的词语的重要性要比IC值较低的词语高。
|
|
|
<br />
|
|
|
然而,信息内容无法满足分析动态改变的非结构化数据的需求。问题:传统语料库和IC元数据都无法随着时间改变,因此任何语料库中不存在的词汇和任何新发明的词汇都会自动得到0的IC值。其次,传统语料库限制了用于文本分析算法 (text analysis algorithm) 的全部知识(语料库代表了整个知识库)。
|
|
|
</p>
|
|
|
<p style="font-weight: 700; font-size: 22px">第三步:词频-逆文档频率(TFIDF - Term Frequency - Inverse Document Frequency)</p>
|
|
|
<p style="font-weight: 600; font-size: 18px">词频 (Term Frequency)</p>
|
|
|
<p>
|
|
|
我们需要一个适应文本上下文(context)和性质(nature)的度量标准 metric (不像IC一样)。
|
|
|
<br />
|
|
|
TFIDF直接作用于所有获取的文档(fetched documents)。一旦获取的文档发生更改,TFIDF可以被轻松更新。TFIDF是一种广泛用于文本分析的措施。TFIDF对动态内容的处理也是相当强大且有效的,因为文档的变化只需要更新频率计数。
|
|
|
<br />
|
|
|
给定一个单词 t 以及一个包含 n 个词的文档 d = {t1, t2, …, tn}。在 d 中 t 最简单的词频形式可以定义为 t 在 d 中出现的次数。
|
|
|
</p>
|
|
|
<img src="@/assets/images/f4003.png" alt="" />
|
|
|
<p>可以对词频函数取对数,以获得更多数据细节。</p>
|
|
|
<img src="@/assets/images/f4005.png" alt="" />
|
|
|
<p>对于长文档,因为其包含的词语数量和种类多,因此往往有更高的词频值。这些因素导致了长文档的词频值的提升,以至于人们对长文档产生了偏见。为了解决这个问题,可以对词频进行规范化。</p>
|
|
|
<img src="@/assets/images/f4006.png" alt="" />
|
|
|
<p>Zipf’s Law:第 i 个最常见的单词出现在最常见词的 1/i 处。</p>
|
|
|
<p style="font-weight: 600; font-size: 18px">Term Frequency 问题</p>
|
|
|
<p>词语的重要性仅取决于其在特定文档中的存在。如果该词语频繁出现在每个文档中怎么办?这个词还重要吗?我们需要拓宽我们的视野,不仅要在单个文档中,而是要在整个语料库中考虑一个词语的重要性。</p>
|
|
|
<p style="font-weight: 600; font-size: 18px">词语的文档频率 (Document Frequency of a term)</p>
|
|
|
<p>文档频率被定义为语料库中包含一个词语的文档的数目。假设语料库D包含N个文档。语料库D={d1, d2, …, dn}中词语 t 的文档频率的定义为:</p>
|
|
|
<img src="@/assets/images/f4007.png" alt="" />
|
|
|
<p style="font-weight: 600; font-size: 18px">逆文档频率 (Inverse Document Frequency)</p>
|
|
|
<p>逆文档频率的定义,N代表了语料库的总文档数,DF(t) 代表单词 t 的文档频率。逆文档频率就是他们的商取对数得到。</p>
|
|
|
<img src="@/assets/images/f4008.png" alt="" />
|
|
|
<p>如果词语不在语料库内,会导致分母为零,快速解决的方法是在分母上加个1。</p>
|
|
|
<img src="@/assets/images/f4009.png" alt="" />
|
|
|
<p>
|
|
|
拥有较高IDF的词往往在整个语料库中更重要。罕见单词的IDF会很高。高频单词的IDF会很低。
|
|
|
<br />
|
|
|
警告,因为语料库中所有文档数目(N)是保持恒定的,因此IDF仅取决于DF,那些DF值最低的词获得相同的最高IDF。因此,有必要引入更多权重词来对文档中出现次数相同的词进行区分。
|
|
|
</p>
|
|
|
<p style="font-weight: 600; font-size: 18px">TFIDF</p>
|
|
|
<p>是一种既考虑词在单个文档中的普及率 prevalence (TF),也考虑他在整个语料库中的稀缺程度 scarcity (IDF)的评估方法。词语 t 在文档 d 中的TFIDF 定义为 d 中 t 的词频 乘以语料库中 t 的文档频率,如下公式:</p>
|
|
|
<img src="@/assets/images/f4010.png" alt="" />
|
|
|
<p>
|
|
|
一个词的TFIDF值越高,它在一个文档中出现的就越频繁,但在语料库的所有文档中出现得就越少。
|
|
|
<br />
|
|
|
TFIDF在简单和直接的计算中是很有效的,因为它不需要文本含义的相关知识。文档 d 被表示为 TFIDF(t, d)值 的高维向量 (high-dimensional vector)。
|
|
|
<br />
|
|
|
<br />
|
|
|
TFIDF方法减少描述长度的数量相对较少,同时其很难揭示文档内或文档之间(intra-document or inter-document)的统计结构。
|
|
|
<br />
|
|
|
主题建模(Topic Modelling)能够解决这个问题。主题(topic): 经常在一起出现的具有相关含义的单词簇(a cluster of words)。每个词在主题中都有权重。
|
|
|
</p>
|
|
|
<p style="font-weight: 700; font-size: 22px">第四步:主题建模(Topic Modelling)</p>
|
|
|
<p>主题建模提供了文档的简短描述。同时,它也提供了自动组织,搜索,理解并汇总大量信息的工具。主题建模是统计模型,它研究一组文档中的单词,确定文档主题,并发现主题是如何相关(associated)或者如何随时间变化的(change over time)。</p>
|
|
|
<p style="font-weight: 600; font-size: 18px">流程</p>
|
|
|
<p>
|
|
|
1.揭开(uncover)语料库中隐藏的主题模式(hidden topical patterns)。
|
|
|
<br />
|
|
|
2.根据这些主题来注释文档(annotate document)。
|
|
|
<br />
|
|
|
3.使用注释来组织,搜索,汇总文档(organize, search, summarise)。
|
|
|
</p>
|
|
|
<p style="font-weight: 600; font-size: 18px">主题(topic)</p>
|
|
|
<p>
|
|
|
一个主题被正式定义为固定单词词汇分布(a distribution over a fixed vocabulary of words)。不同的主题在相同的词汇上会有不同的分布。词汇中的某个词可以在多个主题中有不同的权重。
|
|
|
<br />
|
|
|
一个文档通常由多个主题以不同的比例贯穿文本。
|
|
|
</p>
|
|
|
<p style="font-weight: 600; font-size: 18px">LDA</p>
|
|
|
<p>
|
|
|
最简单的主题模型是Latent Dirichlet Allocation(LDA),这是一个语料库的生成概率模型(generative probabilistic model of a corpus)。在生成概率模型中,从概率密度函数 (probability density function) 得出模型观测值 (model observation)。在LDA中,文档被视为生成过程 (generative process) 的结果(带有隐藏变量 hidden variables)。
|
|
|
<br />
|
|
|
LDA假设了:
|
|
|
<br />
|
|
|
</p>
|
|
|
<li>有一个固定的词汇表 (fixed vocabulary of words)。</li>
|
|
|
<li>潜在主题 (latent topics) 的数目是预定义的。</li>
|
|
|
<li>每个潜在主题的特征都是词汇表中单词的分布(Dirichlet distribution)。</li>
|
|
|
<li>每个文档表示潜在主题的一个随机混合(random mixture)。</li>
|
|
|
<p style="font-weight: 600; font-size: 18px">LDA流程</p>
|
|
|
<p>
|
|
|
1.选择文档的长度N。
|
|
|
<br />
|
|
|
2.每个文档有一个在主题上的分布。
|
|
|
<br />
|
|
|
3.为文档中的每个词分配一个主题,并选择相应的主题中的单词。
|
|
|
<br />
|
|
|
<br />
|
|
|
在现实中,只有文档是可用(available)的。LDA的目标是为每一个文档推断潜在的主题(underlying topic),主题比例 (topic proportion) 以及主题分配 (topic assignment)。
|
|
|
</p>
|
|
|
<p style="font-weight: 600; font-size: 18px">算法</p>
|
|
|
<p>
|
|
|
1.选择一个值 k。
|
|
|
<br />
|
|
|
2.对于每个文档,将文档中的每个单词随机分配给k个主题之一。
|
|
|
<br />
|
|
|
对于每个文档 d,遍历每个单词 w,然后计算 d 中分配给主题 t 的单词所占的比例,通常会应用平滑(smoothing)处理:
|
|
|
</p>
|
|
|
<img src="@/assets/images/f4011.png" alt="" />
|
|
|
<p>
|
|
|
4.计算从单词w来的所有文档中对主题 t 的分配比例 P(w | t)
|
|
|
<br />
|
|
|
5.计算属于主题 t 的单词 w 的概率:
|
|
|
</p>
|
|
|
<img src="@/assets/images/f4012.png" alt="" />
|
|
|
<p style="font-weight: 600; font-size: 18px">注意事项</p>
|
|
|
<p>
|
|
|
LDA将文档视为主题的混合体。
|
|
|
<br />
|
|
|
LDA将主题视为单词的混合体。
|
|
|
<br />
|
|
|
如果单词 w 很有可能出现在主题 t 中,则所有带有 w 的文档将与主题 t 的关联更为紧密。
|
|
|
<br />
|
|
|
如果单词 w 不太可能出现在主题 t 中,则包含 w 的文档出现在 t 中的可能性将会很小。因为文档 d 中剩余的单词将出现在其他主题中,因此 d 有更高的可能性属于那些主题。因此,就算我们将 w 添加到 t 中,也不会将很多该类文档带到 t 。
|
|
|
</p>
|
|
|
<p style="font-weight: 600; font-size: 18px">应用</p>
|
|
|
<p>
|
|
|
主题模型可以被用于文档建模(document modelling),文档分类(document classification)与协同过滤(collaborative filtering)。
|
|
|
<br />
|
|
|
主题模型不仅可以应用于文本数据,它还可以用于注释图像。
|
|
|
</p>
|
|
|
<p style="font-weight: 700; font-size: 22px">情感分析 (Sentiment Analysis)</p>
|
|
|
<p>
|
|
|
情感分析使用统计和自然语言处理来从文本中挖掘观点来识别并抽取主观信息。
|
|
|
<br />
|
|
|
应用: 检测产品或者电影的评论的极性(polarity of reviews)。
|
|
|
<br />
|
|
|
<br />
|
|
|
分析层面包括了文档层面(document),语句层面(sentence),短语层面(phrase),和短文层面(short-text)。
|
|
|
<br />
|
|
|
<br />
|
|
|
分类方法(朴素贝叶斯,最大熵,或者支持向量机SVM) 经常被用来提取语料库统计以用于情感分析。
|
|
|
</p>
|
|
|
<img src="@/assets/images/f4013.png" alt="" style="width: 600px;height: 300px;"/>
|
|
|
<p>分类器仅基于对其进行训练的数据集来确定情感:</p>
|
|
|
<li>词义随着领域不同而改变。</li>
|
|
|
<li>因此模型无法直接应用于其他领域。</li>
|
|
|
<p>
|
|
|
绝对的情感水平无法提供信息,应该建立一条基线,然后将其与观察值比较。比如,一个产品拥有40%正面推文,60%负面推文,貌似是不成功的。但是如果其他类似的成功产品也有类似的正负面百分比,那么不应该认为该产品是不成功的。
|
|
|
<br />
|
|
|
<br />
|
|
|
如何对大量评论添加标签(积极,消极,中立):
|
|
|
<br />
|
|
|
<br />
|
|
|
1.使用表情符号 (小心阴阳怪气)
|
|
|
<br />
|
|
|
2.使用 Amazon Mechanical Turk 互联网众包市场,将上标签工作外包出去。
|
|
|
</p>
|
|
|
<p style="font-weight: 700; font-size: 22px">获得结果(gaining insights)</p>
|
|
|
<p style="font-weight: 600; font-size: 18px">词云(word cloud)</p>
|
|
|
<p>五星好评的词云</p>
|
|
|
<img src="@/assets/images/f4015.png" alt="" style="width: 600px;height: 400px;"/>
|
|
|
<p>一星差评的词云:</p>
|
|
|
<img src="@/assets/images/f4016.png" alt="" />
|
|
|
<p>TFIDF能够用来凸显评论中有信息量的单词。</p>
|
|
|
<img src="@/assets/images/f4017.png" alt="" style="width: 600px;height: 400px;"/>
|
|
|
<p>LDA可以把评论归类为主题。圆盘的大小代表了词的权重。</p>
|
|
|
<img src="@/assets/images/f4018.png" alt="" style="width: 600px;height: 400px;"/>
|
|
|
<p>另一种可视化方式。</p>
|
|
|
<img src="@/assets/images/f4019.png" alt="" style="width: 600px;height: 400px;"/>
|
|
|
</div>
|
|
|
</template>
|
|
|
</popModel>
|
|
|
</template>
|
|
|
<script setup>
|
|
|
import popModel from "@/views/components/popModal.vue";
|
|
|
import { paginate } from "@/utils/index.js";
|
|
|
import * as echarts from "echarts";
|
|
|
import * as XLSX from "XLSX";
|
|
|
import "echarts-wordcloud/dist/echarts-wordcloud";
|
|
|
import "echarts-wordcloud/dist/echarts-wordcloud.min";
|
|
|
import * as portraitModel from "@/api/portraitModel";
|
|
|
import * as API from "@/api/AI.js";
|
|
|
import * as marketingAlgorithmApi from "@/api/marketing-algorithm.js";
|
|
|
import html2canvas from "html2canvas";
|
|
|
import useAlgorithmStore from "@/store/modules/algorithm.js";
|
|
|
import { getUserInfo } from "@/utils/auth";
|
|
|
// 算法知识
|
|
|
const b_algorithmKnowledge = ref(false);
|
|
|
const userInfo = JSON.parse(getUserInfo());
|
|
|
const loading = ref(false);
|
|
|
const loading2 = ref(false);
|
|
|
const { proxy } = getCurrentInstance();
|
|
|
const algorithmStore = useAlgorithmStore();
|
|
|
const multipleTableRef = ref(null);
|
|
|
const n_dataTableQuery = ref({
|
|
|
index: 1,
|
|
|
size: 5,
|
|
|
tableName: algorithmStore.userDataLabel2[0],
|
|
|
userId: userInfo.userId,
|
|
|
});
|
|
|
const textType = [
|
|
|
{ name: "酒店", id: 1 },
|
|
|
{ name: "KTV", id: 2 },
|
|
|
{ name: "丽人", id: 3 },
|
|
|
{ name: "关食", id: 4 },
|
|
|
{ name: "旅游", id: 5 },
|
|
|
{ name: "健束", id: 6 },
|
|
|
{ name: "教育", id: 7 },
|
|
|
{ name: "商业", id: 8 },
|
|
|
{ name: "房产", id: 9 },
|
|
|
{ name: "汽车", id: 10 },
|
|
|
{ name: "生活", id: 11 },
|
|
|
{ name: "购物", id: 12 },
|
|
|
{ name: "3C", id: 13 },
|
|
|
];
|
|
|
const dialogVisible = ref(false);
|
|
|
const analysisData = ref([]);
|
|
|
const analysisHanderKry = ref([]);
|
|
|
const aWordFrequency = ref([]);
|
|
|
const tableLabel = reactive([
|
|
|
{ prop: "max", label: "最多数量词语" },
|
|
|
{ prop: "frequency", label: "频次" },
|
|
|
{ prop: "text1", label: "前两位词语" },
|
|
|
{ prop: "text2", label: "前两位词语" },
|
|
|
{ prop: "emotional", label: "情感倾向分析" },
|
|
|
{ prop: "prominent", label: "中间最突出单词" },
|
|
|
]);
|
|
|
const formInline = ref({
|
|
|
max: "",
|
|
|
frequency: "",
|
|
|
text1: "",
|
|
|
text2: "",
|
|
|
emotional: "",
|
|
|
prominent: "",
|
|
|
});
|
|
|
const g_indicatorData = ref([]);
|
|
|
const preProcessText = ref({
|
|
|
text1: "数据去重",
|
|
|
text2: "",
|
|
|
text3: "",
|
|
|
});
|
|
|
const aOriginalDataOfAnalysisResults2 = ref({});
|
|
|
const nOriginalWordFrequency = ref([]); //词频原始数据
|
|
|
const nOriginalWordFrequencyNum = ref(0); //词频原始数据
|
|
|
const mWordFrequency = ref({
|
|
|
index: 1,
|
|
|
size: 10,
|
|
|
namenum: "",
|
|
|
frequency: "",
|
|
|
}); //词频数据
|
|
|
// 分析变量
|
|
|
const g_analyzeTheNumberOfDataItems = ref("");
|
|
|
// 模型参数
|
|
|
const n_emotiveTendency = ref(0);
|
|
|
const g_emotionAnalysis = ref(0);
|
|
|
|
|
|
const aCommentOpinion = ref([]);
|
|
|
// 下载状态
|
|
|
const downloadStatus = ref(false);
|
|
|
//提交校验
|
|
|
const g_submitVerification = ref({
|
|
|
max: "13",
|
|
|
frequency: "3",
|
|
|
text1: "质量",
|
|
|
text2: "包装好",
|
|
|
emotional: "负向",
|
|
|
prominent: "的",
|
|
|
});
|
|
|
// 预处理
|
|
|
const preProcess = (tetx) => {
|
|
|
if (g_indicatorData.value.length == 0) {
|
|
|
proxy.$modal.msgWarning("请先选择指标进行数据分析!");
|
|
|
preProcessText.value.text2 = "";
|
|
|
return;
|
|
|
}
|
|
|
API.dataPreprocessing({
|
|
|
mapList: analysisData.value,
|
|
|
method: preProcessText.value.text2,
|
|
|
userId: n_dataTableQuery.value.userId,
|
|
|
}).then((res) => {
|
|
|
proxy.$modal.msgSuccess("预处理成功");
|
|
|
analysisData.value = res.data;
|
|
|
g_analyzeTheNumberOfDataItems.value = res.data
|
|
|
.slice(0, 4)
|
|
|
.map((item) => (item ? item.content : ""))
|
|
|
.join(",")
|
|
|
.replace(/\s+/g, "");
|
|
|
});
|
|
|
};
|
|
|
// 提交
|
|
|
const taskSubmit = () => {
|
|
|
dialogVisible.value = true;
|
|
|
};
|
|
|
const headerCellStyle = () => {
|
|
|
return {
|
|
|
backgroundColor: "#1882DE !important", // 设置表头背景颜色
|
|
|
color: "#ffffff !important",
|
|
|
};
|
|
|
};
|
|
|
// 选择指标
|
|
|
const selectType = (type) => {
|
|
|
n_dataTableQuery.value.tableName = type;
|
|
|
getIndicator();
|
|
|
};
|
|
|
// 选择数据
|
|
|
const handleSelectionChange = (type) => {
|
|
|
g_indicatorData.value = type.map((item) => item.label);
|
|
|
};
|
|
|
// 提交指標
|
|
|
const submit = () => {
|
|
|
if (g_indicatorData.value.length == 0) return proxy.$modal.msgWarning("请选择指标");
|
|
|
loading.value = true;
|
|
|
marketingAlgorithmApi.getMarketingAlgorithm({ userId: n_dataTableQuery.value.userId, tableName: n_dataTableQuery.value.tableName, fieldList: g_indicatorData.value }).then((res) => {
|
|
|
if (
|
|
|
res.data
|
|
|
.map((item) => (item ? item.content : ""))
|
|
|
.every((item) => {
|
|
|
return item !== "";
|
|
|
})
|
|
|
) {
|
|
|
g_analyzeTheNumberOfDataItems.value = res.data
|
|
|
.slice(0.4)
|
|
|
.map((item, index) => (item ? item.content : ""))
|
|
|
.slice(0, 4)
|
|
|
.join(",")
|
|
|
.replace(/\s+/g, "");
|
|
|
} else {
|
|
|
g_analyzeTheNumberOfDataItems.value = "";
|
|
|
loading.value = false;
|
|
|
return;
|
|
|
}
|
|
|
analysisData.value = res.data.filter((item) => item !== null);
|
|
|
loading.value = false;
|
|
|
proxy.$modal.msgSuccess("分析数据成功");
|
|
|
});
|
|
|
};
|
|
|
onMounted(() => {
|
|
|
getIndicator();
|
|
|
});
|
|
|
// 获取指标
|
|
|
const getIndicator = () => {
|
|
|
algorithmStore.getUserDatabase({ userId: n_dataTableQuery.value.userId, tableName: n_dataTableQuery.value.tableName, algorithmName: "情感分析" });
|
|
|
};
|
|
|
// 算法知识导入
|
|
|
const knowledgeImport = () => {
|
|
|
b_algorithmKnowledge.value = true;
|
|
|
};
|
|
|
// 实训任务提交
|
|
|
const submitTask = () => {
|
|
|
let flat = false;
|
|
|
for (let key in formInline.value) {
|
|
|
if (formInline.value[key] == "") {
|
|
|
const label = tableLabel.find((item) => item.prop == key);
|
|
|
proxy.$modal.msgWarning(`${label.label}不能为空`);
|
|
|
return;
|
|
|
} else {
|
|
|
if (formInline.value[key] != g_submitVerification.value[key]) {
|
|
|
const label = tableLabel.find((item) => item.prop == key);
|
|
|
proxy.$modal.msgWarning(`${label.label}错误`);
|
|
|
flat = true;
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
portraitModel
|
|
|
.submit({
|
|
|
userId: n_dataTableQuery.value.userId,
|
|
|
taskName: "情感分析",
|
|
|
numberOfErrors: flat ? 1 : 0,
|
|
|
})
|
|
|
.then((res) => {
|
|
|
dialogVisible.value = false;
|
|
|
proxy.$modal.msgSuccess("提交成功");
|
|
|
});
|
|
|
};
|
|
|
// 重新开始
|
|
|
const restart = () => {
|
|
|
// 清空
|
|
|
formInline.value = {
|
|
|
max: "",
|
|
|
frequency: "",
|
|
|
text1: "",
|
|
|
text2: "",
|
|
|
emotional: "",
|
|
|
prominent: "",
|
|
|
};
|
|
|
analysisData.value = [];
|
|
|
g_analyzeTheNumberOfDataItems.value = "";
|
|
|
analysisHanderKry.value = [];
|
|
|
g_indicatorData.value = [];
|
|
|
preProcessText.value = {
|
|
|
text1: "数据去重",
|
|
|
text2: "",
|
|
|
text3: "",
|
|
|
};
|
|
|
aOriginalDataOfAnalysisResults2.value = {};
|
|
|
mWordFrequency.value = {
|
|
|
index: 1,
|
|
|
size: 10,
|
|
|
namenum: "",
|
|
|
frequency: "",
|
|
|
};
|
|
|
n_emotiveTendency.value = 0;
|
|
|
aCommentOpinion.value = [];
|
|
|
nOriginalWordFrequencyNum.value = 0;
|
|
|
aWordFrequency.value = [];
|
|
|
nOriginalWordFrequency.value = [];
|
|
|
proxy.$modal.msgSuccess("重新开始!");
|
|
|
multipleTableRef.value.clearSelection();
|
|
|
};
|
|
|
const captureElement = ref(null);
|
|
|
// 下载
|
|
|
const download = async () => {
|
|
|
if (!downloadStatus.value) return proxy.$modal.msgWarning("请先分析数据!");
|
|
|
if (preProcessText.value.text3 == "情感倾向分析" || preProcessText.value.text3 == "评论观点抽取") {
|
|
|
if (captureElement.value) {
|
|
|
try {
|
|
|
// 使用 html2canvas 将元素转换为画布
|
|
|
let VDOM = document.querySelector(".emotion_analysis_title");
|
|
|
let VDOM2 = document.getElementsByClassName("result");
|
|
|
if (preProcessText.value.text3 == "情感倾向分析") {
|
|
|
VDOM.style.color = "#221f1f";
|
|
|
} else {
|
|
|
for (let i = 0; i < VDOM2.length; i++) {
|
|
|
const element = VDOM2[i];
|
|
|
element.style.color = "#221f1f";
|
|
|
}
|
|
|
}
|
|
|
const canvas = await html2canvas(captureElement.value);
|
|
|
|
|
|
// 将画布转换为图片
|
|
|
const image = canvas.toDataURL("image/png");
|
|
|
// 创建一个链接,设置为图片的下载链接
|
|
|
const link = document.createElement("a");
|
|
|
link.href = image;
|
|
|
link.download = `${preProcessText.value.text3}.png`; // 图片的文件名
|
|
|
// 触发点击事件,下载图片
|
|
|
link.click();
|
|
|
if (preProcessText.value.text3 == "情感倾向分析") {
|
|
|
VDOM.style.color = "#ffffff";
|
|
|
} else {
|
|
|
for (let i = 0; i < VDOM2.length; i++) {
|
|
|
const element = VDOM2[i];
|
|
|
element.style.color = "#ffffff";
|
|
|
}
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error("无法生成图片", error);
|
|
|
}
|
|
|
}
|
|
|
} else if (preProcessText.value.text3 == "词频分析") {
|
|
|
const aoaData = [
|
|
|
["内容", "频次"], // 标题行
|
|
|
...nOriginalWordFrequency.value.map((item) => [item.keyword, item.frequency]), // 数据行
|
|
|
];
|
|
|
const worksheet = XLSX.utils.aoa_to_sheet(aoaData);
|
|
|
const workbook = XLSX.utils.book_new();
|
|
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1");
|
|
|
XLSX.writeFile(workbook, "词频分析.xlsx");
|
|
|
}
|
|
|
};
|
|
|
// 情感分析
|
|
|
const sentimentAnalysis = () => {
|
|
|
let g_emotionAnalysisinfo = {};
|
|
|
if (g_indicatorData.value.length > 0) {
|
|
|
if (preProcessText.value.text2 == "") return proxy.$modal.msgWarning("请先对数据进行预处理!");
|
|
|
}
|
|
|
if (preProcessText.value.text3 == "") return proxy.$modal.msgWarning("请先选择模型!");
|
|
|
|
|
|
if (g_analyzeTheNumberOfDataItems.value == "") return proxy.$modal.msgWarning("请先选择指标/手动输入进行情感分析!");
|
|
|
loading.value = true;
|
|
|
if (preProcessText.value.text3 === "情感倾向分析") {
|
|
|
marketingAlgorithmApi
|
|
|
.getMarketingAlgorithmSentimentAnalysis(JSON.stringify({ text: g_analyzeTheNumberOfDataItems.value }))
|
|
|
.then((res) => {
|
|
|
g_emotionAnalysisinfo = res.data;
|
|
|
const pos = parseFloat(g_emotionAnalysisinfo.pos);
|
|
|
const neg = parseFloat(g_emotionAnalysisinfo.neg);
|
|
|
const total = pos + neg;
|
|
|
const posPercentage = total !== 0 ? (pos / total) * 100 : 0;
|
|
|
const negPercentage = total !== 0 ? (neg / total) * 100 : 0;
|
|
|
if (posPercentage > negPercentage) {
|
|
|
g_emotionAnalysis.value = 2;
|
|
|
n_emotiveTendency.value = Math.floor(posPercentage);
|
|
|
} else if (negPercentage > posPercentage) {
|
|
|
g_emotionAnalysis.value = 0;
|
|
|
n_emotiveTendency.value = Math.floor(posPercentage);
|
|
|
}
|
|
|
if (posPercentage == negPercentage) {
|
|
|
g_emotionAnalysis.value = 1;
|
|
|
n_emotiveTendency.value = 50;
|
|
|
}
|
|
|
loading.value = false;
|
|
|
downloadStatus.value = true;
|
|
|
proxy.$modal.msgSuccess("分析成功");
|
|
|
})
|
|
|
.catch((err) => {
|
|
|
loading.value = false;
|
|
|
});
|
|
|
} else if (preProcessText.value.text3 !== "评论观点抽取") {
|
|
|
marketingAlgorithmApi
|
|
|
.getMarketingAlgorithmSentiment({ userId: n_dataTableQuery.value.userId, content: g_analyzeTheNumberOfDataItems.value, modelType: preProcessText.value.text3 == "词云生成" ? "词频分析" : preProcessText.value.text3 })
|
|
|
.then((res) => {
|
|
|
if (preProcessText.value.text3 === "词云生成") {
|
|
|
nOriginalWordFrequency.value = res.data;
|
|
|
initChart();
|
|
|
} else if (preProcessText.value.text3 === "词频分析") {
|
|
|
nOriginalWordFrequency.value = res.data;
|
|
|
aWordFrequency.value = res.data.slice(0, 5);
|
|
|
nOriginalWordFrequencyNum.value = res.data.length;
|
|
|
}
|
|
|
downloadStatus.value = true;
|
|
|
g_submitVerification.value.n_maximumQuantity = nOriginalWordFrequency.value.reduce((longest, current) => {
|
|
|
return current.keyword.length > longest.keyword.length ? current.keyword : longest.keyword;
|
|
|
}, nOriginalWordFrequency.value[0]);
|
|
|
g_submitVerification.value.n_frequency = nOriginalWordFrequency.value.reduce((longest, current) => {
|
|
|
return item.frequency > max.frequency ? item.n_frequency : max.n_frequency;
|
|
|
}, nOriginalWordFrequency.value[0]);
|
|
|
console.log(g_submitVerification.value, "g_submitVerification.value.");
|
|
|
loading.value = false;
|
|
|
proxy.$modal.msgSuccess("分析成功");
|
|
|
})
|
|
|
.catch((err) => {
|
|
|
loading.value = false;
|
|
|
});
|
|
|
} else {
|
|
|
portraitModel
|
|
|
.getCommentPoint(JSON.stringify({ text: g_analyzeTheNumberOfDataItems.value }))
|
|
|
.then((res) => {
|
|
|
aCommentOpinion.value = res.singleAnalysisResult.reduce((groups, item) => {
|
|
|
if (!groups[item.sentiment == "正向" ? 2 : 0]) {
|
|
|
groups[item.sentiment == "正向" ? 2 : 0] = [];
|
|
|
}
|
|
|
groups[item.sentiment == "正向" ? 2 : 0].push(item);
|
|
|
return groups;
|
|
|
}, {});
|
|
|
downloadStatus.value = true;
|
|
|
loading.value = false;
|
|
|
proxy.$modal.msgSuccess("分析成功");
|
|
|
})
|
|
|
.catch((err) => {
|
|
|
loading.value = false;
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
// 清空
|
|
|
const clear = () => {
|
|
|
if (!g_analyzeTheNumberOfDataItems.value) return proxy.$modal.msgWarning("请先分析数据!");
|
|
|
g_analyzeTheNumberOfDataItems.value = "";
|
|
|
aWordFrequency.value = [];
|
|
|
loading.value = false;
|
|
|
proxy.$modal.msgSuccess("清空成功");
|
|
|
};
|
|
|
const initChart = () => {
|
|
|
var myChart = echarts.init(document.getElementById("mywordcloud_positive"));
|
|
|
const arr = nOriginalWordFrequency.value.map((item) => {
|
|
|
return {
|
|
|
name: item.keyword,
|
|
|
value: item.frequency,
|
|
|
};
|
|
|
});
|
|
|
myChart.setOption({
|
|
|
title: {
|
|
|
text: "词云分析",
|
|
|
x: "center",
|
|
|
textStyle: {
|
|
|
fontSize: 23,
|
|
|
},
|
|
|
},
|
|
|
backgroundColor: "#ffffff",
|
|
|
tooltip: {
|
|
|
show: true,
|
|
|
},
|
|
|
toolbox: {
|
|
|
feature: {
|
|
|
saveAsImage: {
|
|
|
iconStyle: {
|
|
|
normal: {
|
|
|
color: "#FFFFFF",
|
|
|
},
|
|
|
},
|
|
|
},
|
|
|
},
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
{
|
|
|
name: "词云分析",
|
|
|
type: "wordCloud",
|
|
|
// //size: ['9%', '99%'],
|
|
|
sizeRange: [6, 66],
|
|
|
// //textRotation: [0, 45, 90, -45],
|
|
|
rotationRange: [-45, 90],
|
|
|
// //shape: 'circle',
|
|
|
// gridSize: 5,
|
|
|
// sizeRange: [12, 32],
|
|
|
width: "90%",
|
|
|
height: "86%",
|
|
|
textPadding: 0,
|
|
|
autoSize: {
|
|
|
enable: true,
|
|
|
minSize: 16,
|
|
|
},
|
|
|
textStyle: {
|
|
|
color: function () {
|
|
|
return "rgb(" + [Math.round(Math.random() * 160), Math.round(Math.random() * 160), Math.round(Math.random() * 160)].join(",") + ")";
|
|
|
},
|
|
|
emphasis: {
|
|
|
shadowBlur: 10,
|
|
|
shadowColor: "#333",
|
|
|
},
|
|
|
},
|
|
|
data: arr,
|
|
|
},
|
|
|
],
|
|
|
});
|
|
|
};
|
|
|
|
|
|
const wordFrequencyNext = () => {
|
|
|
if (mWordFrequency.value.frequency !== "" || mWordFrequency.value.namenum !== "") {
|
|
|
wordFrequencySearch();
|
|
|
} else {
|
|
|
aWordFrequency.value = paginate(nOriginalWordFrequency.value, mWordFrequency.value.index, mWordFrequency.value.size).data;
|
|
|
}
|
|
|
};
|
|
|
const wordFrequencySearch = () => {
|
|
|
// 根据频次和词语长度进行查找
|
|
|
loading2.value = true;
|
|
|
const filteredData = nOriginalWordFrequency.value.filter((item) => {
|
|
|
const matchesKeyword1 = mWordFrequency.value.frequency ? item.frequency === Number(mWordFrequency.value.frequency) : true;
|
|
|
const matchesKeyword2 = mWordFrequency.value.namenum ? item.keyword.length == mWordFrequency.value.namenum : true;
|
|
|
return matchesKeyword1 && matchesKeyword2;
|
|
|
});
|
|
|
aWordFrequency.value = filteredData.length > 5 ? paginate(filteredData, mWordFrequency.value.index, mWordFrequency.value.size).data : filteredData;
|
|
|
nOriginalWordFrequencyNum.value = filteredData.length;
|
|
|
setTimeout(() => {
|
|
|
loading2.value = false;
|
|
|
}, 600);
|
|
|
};
|
|
|
const switchingModels = () => {
|
|
|
// 清空右边的数据
|
|
|
n_emotiveTendency.value = 0;
|
|
|
aCommentOpinion.value = [];
|
|
|
nOriginalWordFrequencyNum.value = 0;
|
|
|
aWordFrequency.value = [];
|
|
|
nOriginalWordFrequency.value = [];
|
|
|
mWordFrequency.value.index = 1;
|
|
|
mWordFrequency.value.size = 5;
|
|
|
mWordFrequency.value.frequency = "";
|
|
|
mWordFrequency.value.namenum = "";
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.main-top {
|
|
|
margin: 20px 20px 0;
|
|
|
margin-top: 10px;
|
|
|
height: 85px;
|
|
|
background: #001d40;
|
|
|
border: 1px solid #0452c6;
|
|
|
padding: 5px 20px;
|
|
|
}
|
|
|
.app-main {
|
|
|
margin: 0px 20px 0;
|
|
|
height: 100%;
|
|
|
display: flex;
|
|
|
overflow: auto;
|
|
|
.main-left {
|
|
|
//水平居中
|
|
|
flex-direction: column;
|
|
|
// min-height: 965px;
|
|
|
display: flex;
|
|
|
width: 205px;
|
|
|
height: 100%;
|
|
|
background: #041c49;
|
|
|
padding: 0px 10px;
|
|
|
.left-top {
|
|
|
padding: 10px;
|
|
|
span {
|
|
|
font-weight: bold;
|
|
|
font-size: 18px;
|
|
|
color: #3596eb;
|
|
|
}
|
|
|
}
|
|
|
.metrics-table {
|
|
|
.el-button {
|
|
|
width: 149px;
|
|
|
height: 48px;
|
|
|
background: url("../../../assets/images/确定.png");
|
|
|
color: #ffffff;
|
|
|
border-color: #ffffff00;
|
|
|
background-size: 100% 100%;
|
|
|
margin: 15px;
|
|
|
}
|
|
|
}
|
|
|
.startOver {
|
|
|
margin-left: -10px;
|
|
|
width: 205px;
|
|
|
height: 55px;
|
|
|
background: #00f4ff;
|
|
|
//水平居中
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
:deep(.el-table) {
|
|
|
--el-table-row-hover-bg-color: #002651 !important;
|
|
|
--el-table-border-color: #7ac1ff;
|
|
|
.el-table__row {
|
|
|
background-color: #002652 !important;
|
|
|
color: #e6e6e6;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.main-right {
|
|
|
width: 100%;
|
|
|
// height: 100%;
|
|
|
// padding: 20px;
|
|
|
border: 1px solid #0452c6;
|
|
|
.text-type {
|
|
|
margin: 29px 0 20px 0px;
|
|
|
transition: all 3s;
|
|
|
.el-button {
|
|
|
width: 90px;
|
|
|
height: 28px;
|
|
|
--el-button-bg-color: #041d3b;
|
|
|
--el-button-border-color: #1983dd;
|
|
|
}
|
|
|
.btnActive {
|
|
|
background: #409eff;
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
}
|
|
|
.analysis {
|
|
|
margin-top: 10px;
|
|
|
:deep(.el-textarea) {
|
|
|
height: 343px;
|
|
|
background: #002652;
|
|
|
border-radius: 10px;
|
|
|
.el-textarea__inner {
|
|
|
min-height: 343px !important;
|
|
|
background: transparent;
|
|
|
box-shadow: 0 0 0 0px #1983dd !important;
|
|
|
border: 1px solid #1983dd;
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
.el-input__count {
|
|
|
background: transparent !important;
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.footer {
|
|
|
padding-top: 20px;
|
|
|
text-align: center;
|
|
|
.el-button {
|
|
|
width: 110px;
|
|
|
height: 31px;
|
|
|
color: #333333;
|
|
|
border: none;
|
|
|
// 获取焦点
|
|
|
&:focus {
|
|
|
outline: none;
|
|
|
}
|
|
|
}
|
|
|
.el-button:nth-child(1) {
|
|
|
background: url("../../../assets/images/情感分析.png") no-repeat;
|
|
|
background-size: 100% 100%;
|
|
|
}
|
|
|
.el-button:nth-child(2) {
|
|
|
background: url("../../../assets/images/取消.png") no-repeat;
|
|
|
background-size: 100% 100%;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.metrics,
|
|
|
.metrics-table {
|
|
|
:deep(.el-input) {
|
|
|
.el-input__wrapper {
|
|
|
background-color: #ffffff00;
|
|
|
background-image: url("../../../assets/images/下拉框.png");
|
|
|
background-size: 100% 100%;
|
|
|
}
|
|
|
--el-input-text-color: #ffffff;
|
|
|
--el-input-border-color: #ffffff00;
|
|
|
--el-input-hover-border-color: #ffffff00;
|
|
|
--el-input-hover-border: #ffffff00;
|
|
|
--el-select-border-color-hover: #ffffff00;
|
|
|
--el-select-input-focus-border-color: #ffffff00;
|
|
|
--el-input-placeholder-color: #ffffff;
|
|
|
.el-input__inner {
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.analysisResults {
|
|
|
transition: all 6s;
|
|
|
|
|
|
.el-button {
|
|
|
width: 89px;
|
|
|
height: 31px;
|
|
|
background: url("../../../assets/images/下载.png");
|
|
|
color: black;
|
|
|
border-color: #ffffff00;
|
|
|
// background-size: 100% 100%;
|
|
|
margin: 15px 0px 5px;
|
|
|
}
|
|
|
.conditions {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
color: #ffffff;
|
|
|
font-size: 14px;
|
|
|
margin: 16px 0;
|
|
|
label {
|
|
|
width: 80px;
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
div {
|
|
|
span {
|
|
|
display: inline-block;
|
|
|
color: #ffffff;
|
|
|
margin-right: 10px;
|
|
|
}
|
|
|
:deep(.el-input) {
|
|
|
.el-input__wrapper {
|
|
|
background-color: #002651 !important;
|
|
|
box-shadow: 0 0 0 1px #67b3d6;
|
|
|
border: radius 0px;
|
|
|
.el-input__inner {
|
|
|
color: #ffffff;
|
|
|
caret-color: #ffffff;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
margin-right: 55px;
|
|
|
}
|
|
|
}
|
|
|
.emotion_analysis {
|
|
|
position: relative;
|
|
|
display: flex;
|
|
|
// align-items: center;
|
|
|
justify-content: center;
|
|
|
gap: 30px;
|
|
|
color: #ffffff;
|
|
|
font-size: 14px;
|
|
|
height: 180px;
|
|
|
margin-top: 40px;
|
|
|
.emotion_analysis-ani-in-ri,
|
|
|
.emotion_analysis-ani-in-le {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
.emotion_analysis_title {
|
|
|
position: absolute;
|
|
|
top: 0px;
|
|
|
right: 48%;
|
|
|
font-size: 24px;
|
|
|
font-weight: normal;
|
|
|
}
|
|
|
:deep(.el-progress) {
|
|
|
position: relative;
|
|
|
}
|
|
|
:deep(.el-progress-bar__outer) {
|
|
|
background-color: #036fe2;
|
|
|
}
|
|
|
:deep(.negPercentage) {
|
|
|
.el-progress-bar__inner {
|
|
|
background-color: #036fe2 !important;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.comment-opinion {
|
|
|
padding: 30px 20px 0px 90px !important;
|
|
|
|
|
|
.result {
|
|
|
height: 40px;
|
|
|
-webkit-text-size-adjust: 100%;
|
|
|
font: 14px BlinkMacSystemFont, Roboto, Helvetica Neue, Helvetica, PingFangSC-Regular, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
|
|
|
-webkit-tap-highlight-color: transparent;
|
|
|
-webkit-font-smoothing: antialiased;
|
|
|
font-family: -apple-system, BlinkMacSystemFont, SF Pro SC, SF Pro Text, Helvetica Neue, Helvetica, PingFang SC, Segoe UI, Roboto, Hiragino Sans GB, arial, microsoft yahei ui, Microsoft YaHei, SimSun, sans-serif;
|
|
|
font-weight: 400;
|
|
|
position: relative;
|
|
|
padding: 8px 0 8px 55px;
|
|
|
margin-right: 1px;
|
|
|
color: #ffffff;
|
|
|
.result-img {
|
|
|
-webkit-text-size-adjust: 100%;
|
|
|
font: 14px BlinkMacSystemFont, Roboto, Helvetica Neue, Helvetica, PingFangSC-Regular, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
|
|
|
-webkit-tap-highlight-color: transparent;
|
|
|
-webkit-font-smoothing: antialiased;
|
|
|
font-family: -apple-system, BlinkMacSystemFont, SF Pro SC, SF Pro Text, Helvetica Neue, Helvetica, PingFang SC, Segoe UI, Roboto, Hiragino Sans GB, arial, microsoft yahei ui, Microsoft YaHei, SimSun, sans-serif;
|
|
|
font-weight: 400;
|
|
|
position: absolute;
|
|
|
top: 8px;
|
|
|
left: 0;
|
|
|
width: 26px;
|
|
|
height: 26px;
|
|
|
border-radius: 50%;
|
|
|
background-size: contain;
|
|
|
background-repeat: no-repeat;
|
|
|
background-image: url(../../../assets/images/笑脸.png);
|
|
|
background-color: #ff6b0f;
|
|
|
}
|
|
|
.result-neg {
|
|
|
position: absolute;
|
|
|
top: 8px;
|
|
|
left: 0;
|
|
|
width: 26px;
|
|
|
height: 26px;
|
|
|
border-radius: 50%;
|
|
|
background-size: contain;
|
|
|
background-repeat: no-repeat;
|
|
|
background-image: url(../../../assets/images/哭脸.png);
|
|
|
background-color: #60aefd;
|
|
|
}
|
|
|
.result-title {
|
|
|
line-height: 26px;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
.result-view {
|
|
|
position: relative;
|
|
|
display: inline-block;
|
|
|
padding: 0 10px;
|
|
|
margin-right: 2px;
|
|
|
line-height: 26px;
|
|
|
font-size: 14px;
|
|
|
top: -26px;
|
|
|
left: 61px;
|
|
|
.result-bar {
|
|
|
position: absolute;
|
|
|
top: 50%;
|
|
|
right: -1px;
|
|
|
transform: translateY(-50%);
|
|
|
height: 15px;
|
|
|
width: 1px;
|
|
|
color: #d8d8d8;
|
|
|
border-right: 1px solid #d8d8d8;
|
|
|
}
|
|
|
}
|
|
|
.result-view:last-child {
|
|
|
.result-bar {
|
|
|
border: none;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
:deep(.el-form-item__label) {
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
.from-item {
|
|
|
padding: 20px;
|
|
|
.el-input {
|
|
|
--el-input-text-color: #ffffff;
|
|
|
--el-input-bg-color: #002651;
|
|
|
--el-input-border-color: #67b3d6;
|
|
|
}
|
|
|
}
|
|
|
.dialog-footer {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
h4,
|
|
|
p {
|
|
|
// color: #ffffff !important;
|
|
|
// font-size: 14px;
|
|
|
}
|
|
|
:deep(.pagination-container .el-pagination) {
|
|
|
// 去除定位
|
|
|
position: static;
|
|
|
}
|
|
|
:deep(.demo-form-inline) {
|
|
|
.el-form-item__content {
|
|
|
flex-wrap: nowrap !important;
|
|
|
}
|
|
|
}
|
|
|
:deep(.el-radio-group) {
|
|
|
// 添加颜色类
|
|
|
.is-checked.el-radio__label {
|
|
|
color: #ffffff !important;
|
|
|
}
|
|
|
.el-radio__input.is-checked .el-radio__inner {
|
|
|
background: #2d5c8f !important;
|
|
|
border-color: #2d5c8f !important;
|
|
|
}
|
|
|
.el-radio__label {
|
|
|
color: #ffffff !important;
|
|
|
}
|
|
|
.el-radio__input .el-radio__inner {
|
|
|
background: #2d5c8f !important;
|
|
|
border-color: #2d5c8f !important;
|
|
|
border-color: #6fa8de !important;
|
|
|
}
|
|
|
}
|
|
|
p,
|
|
|
h4 {
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
</style>
|