TESG事件雷达主题分析

本文重点概要:

  • 文章难度:★★★☆☆
  • 使用「TESG事件雷达」进行主题分类

前言

随著工业化的蓬勃发展,人们发现全球各地渐渐发生的气候变化会对自然环境、社会及全球经济会产生严重的影响,例如极端气候、海平面上升与空气污染等现象,各国开始思考要怎么与自然环境共存,因此联合国在2005年提出企业应该将「环境」、「社会责任」与「公司治理」,也就是大家常说的 “ESG” 纳入企业经营的评量基准,希望可对社会及金融市场、个人的投资组合产生正面影响,也使企业也必须思考要如何在不断变化商业环境中维持顾营收成长的情况下同时达成永续经营的目标。

但是在市场中,每天都有数也数不清的消息资讯出现在市场上,透过个人想了解全部的资讯细节著实困难,因此今天的主题就是透过「TESG事件雷达」搭配主题分析帮助我们快速了解 各大政府公开资讯平台、股东会年报、企业永续报告书所讨论的 ESG 议题为何。

背景知识

普遍来说,一个文本会是由多个主题组成,而且每个主题所占的文章比例各不相同,因此文本中相应主题的关键字出现次数也会有所不同,主题模型来可以用于分析文本内的字词,以统计的方式来计算文本可能属于每个主题的机率分布。

Latent Dirichlet Allocation ( 隐含狄利克雷分配 ) 是一种非监督式主题模型,是一般化的PLSI ( 机率潜在语义分析 )。它用于按主题收集、分类和降低文本的维度。 LDA 是一种主题模型方法,可用于分析文本的主题分布,以机率分布的形式表达每个文本的主题。

编辑环境及模组需求

本文使用Mac OS并以jupyter作为编辑器

import tejapi

# 前处理套件
import pandas as pd 
import re
import numpy as np 
from datetime import datetime
from ckip_transformers.nlp import CkipWordSegmenter, CkipPosTagger, CkipNerChunker

# 模型套件
from numba import jit, cuda
import gensim
from gensim import corpora, models
from gensim.models.coherencemodel import CoherenceModel
from gensim.models.ldamodel import LdaModel

# 视觉化套件
import matplotlib
import matplotlib.pyplot as plt
import pyLDAvis.gensim_models
from wordcloud import WordCloud


# 辅助套件
import warnings
warnings.filterwarnings("ignore")

tejapi.ApiConfig.api_key = "Your Key"
tejapi.ApiConfig.ignoretz = True

资料库使用

TESG事件雷达 ( TWN/AEWATCHA )

资料导入

资料期间从 2022–01–01 至 2023–04–01,以台积电 (2330) 作为实例,抓取事件内容作为分析资料。

stock_id = '2330', '2303', '2881', '3045'
gte, lte = '2022-01-01', '2023-04-01'
TESG = tejapi.get('TWN/AEWATCHA',
                   paginate = True,
                   coid = stock_id,
                   mdate = {'gte':gte, 'lte':lte},
                  )

df = TESG
df["mdate"] = pd.to_datetime(df["mdate"])
TESG事件雷达:资料栏位及型态

前处理

本次实作我们使用中研院研发的NLP套件 「ckip_transformers」来作为文章断词与词性标注的工具,原因是「ckip_transformers」的断词结果相较于「jieba」、「SnowNLP」等基于中国中文用法开发的套件,断词结果更接近台湾人的习惯。

# Initialize drivers
print("Initializing drivers ... WS")
# device=0 为使用gpu进行运算,如电脑无gpu者可改为 device=-1 用cpu运算
ws_driver = CkipWordSegmenter(model="albert-base", device=0)
print("Initializing drivers ... POS")
pos_driver = CkipPosTagger(model="bert-base", device=0)
print("Initializing drivers ... NER")

接著就对文章进行断词与词性标注,这里使用 python 中的 lambda 函式来对每一笔资料进行处理,其优点是效能高且程式码简单,相较于 for 回圈能更快完成大量资料处理。

df["seg"] = list(map(lambda x: ws_driver([x]), list(df["newstxt_1"])))
df["seg"] = df["seg"].apply(lambda x : x[0])
df["pos"] = df["seg"].apply(lambda x : pos_driver(x))
文章断词与词性标注结果

由上图我们可以发现经过词性标注后,单一单词有可能拥有多种词性,而对于大多数的语言,句子的意义主要集中在名词及动词上,因此下一步我们需要过滤出断词中为名词或动词的单词,并将结果放入一个新的栏位「N_or_V」。

# 词性过滤
def fltr_nv(word_lst, pos_lst):
  lst = []
  for word, pos in zip(word_lst, pos_lst):
    for i in pos:
      if i.startswith(("N", "V")):
        lst.append(word)
        break
  return lst

df["N_or_V"] = df.apply(lambda x : fltr_nv(x["seg"], x["pos"]), axis = 1)

再来我们筛查看其中资料分布的月份。

# 计算每月文章数
gb_corp = df_corp[["mdate", "N_or_V"]].groupby([df.mdate.dt.year, df.mdate.dt.month])
a = 0
lst = []

for group_key, group_value in gb_corp:
    group = gb_corp.get_group(group_key)
    dct = {
        "month" : datetime.strptime(str(group['mdate'].iloc[0])[:7], "%Y-%m"),
        "key_word" : [i for i in group['N_or_V']]
    }
    lst.append(dct)

    print(f"{group_key} : {len(group)}")
    a+=len(group)
print(a)
台积电每月资料数量
台积电每月资料数

我们透过文字云来观察每篇文章的重点单字,可以看到虽然我们未使用 TF-IDF 、Rext Rank等等关键字撷取的方法,仅仅经过词性筛选所得到的结果看似已经非常不错了。

!wget https://raw.githubusercontent.com/victorgau/wordcloud/master/SourceHanSansTW-Regular.otf -o /dev/null
%matplotlib inline

# 从 Google 下载的中文字型
font = 'SourceHanSansTW-Regular.otf'

df_keyword = pd.DataFrame(lst)
df_keyword["key_word"] = df_keyword["key_word"].apply(lambda x : " ".join(x[0]))
df_keyword["pic"] = df_keyword["key_word"].apply(lambda x : WordCloud(font_path=font, max_words = 20, background_color = "white").generate(x))

plt.imshow(df_keyword["pic"].iloc[10])
plt.axis("off")
plt.show()
文字云
文字云

模型建置

我们使用 gensim 这个套件来进行 LDA 模型的建置,首先第一步必须建立字典并且为字典中的每个单字给予相对应的编号,再计算每个编号 ( 单词 ) 在全部文章中出现的次数。

# 将过滤后的单词转换为转换为list of list形式
seg_lst = list(df_corp["N_or_V"])

# corpora.Dictionary() input 是文字的 list of list 
dictionary = corpora.Dictionary(seg_lst)
# corpus为 (编号:出现字数) 的 list of list
corpus = [dictionary.doc2bow(i) for i in seg_lst]

接下来就要来建立模型了,这边遇到一个问题,由于 LDA 主题分类需要事先给定要分类的主题数才能运作,但究竟分类成几类才是最恰当的呢?
这里我们使用 Log Perplexity 与 Topic Coherence 来衡量分类数量。

Log Perplexity 解释了模型预测结果中的 “不确定性” 水准,也就是对于一篇文章来说,我们有多不确定 它是属于某个主题的,所以主题的个数越多,模型的困惑度就越低,但要注意的是,当主题数很多的时候,生成的模型往往会过拟合,所以不能单纯依靠困惑度来判断一个模型的好坏。

相比之下,Topic Coherence 会衡量主题中高分词之间的语义相似度,这些测量有助于区分 语义可解释的主题 基于统计推断的主题,分数越高代表主题之间的一致性越低。通常,一致性分数会随著主题数量的增加而增加。 随著主题数量的增加,一致性分数增加幅度会递减。 这时经常使用 elbow technique ( 手肘法 ) 在主题数量和连贯性得分之间进行权衡。

而关于具体这两个方法为何能帮助我们判断主题个数的原因在这里就不细说了,目前需要的概念只有Log Perplexity越低代表分类效果越好、Topic Coherence越高代表分类效果越好

这里要提醒一点,没有一种准则说 Perplexity、Coherence 应该落在哪里才是好的,我们得到分数及其价值取决于其计算的资料。 例如,在一种情况下,0.5的分数可能足够好,但在另一种情况下是不可接受的。
唯一的规则是,我们希望把 Perplexity 分数最小化、Coherence分数最大化。

# 困惑度计算
def perplexity(num_topics):
    ldamodel = LdaModel(corpus, num_topics=num_topics, id2word=dictionary, passes = 30)
    print(ldamodel.print_topics(num_topics = num_topics, num_words = 15))
    print(ldamodel.log_perplexity(corpus))
    return ldamodel.log_perplexity(corpus)

# 主题一致性计算
def coherence(num_topics):
    ldamodel = LdaModel(corpus, num_topics = num_topics, id2word = dictionary, passes = 30, random_state = 42)
    print(ldamodel.print_topics(num_topics = num_topics, num_words = 15))
    ldacm = CoherenceModel(model = ldamodel, texts = seg_lst, dictionary = dictionary, coherence="c_v")
    print(ldacm.get_coherence())
    return ldacm.get_coherence()
TESG事件雷达:topic-perplexity variation
TESG事件雷达:topic-perplexity variation

由上图我们可以发现 Log Perplexity 在主题数为 9 之后会急速下降,这隐含的意义为模型可能产生过拟合的情形,所以我们选择的主题数应该要小于 10 ,因此 Topic Coherence 我们将给定的主题数为 1 ~ 9 进行计算。

TESG事件雷达:topic-coherence variation
 TESG事件雷达:topic-coherence variation

结果显示当主题数为 7 时,分类模型著最高的分数,如此一来我们就确定了应该要将文章分类成 7 类,接著就将主题数输入模型。

num_topics = 7
lda = LdaModel(corpus, num_topics = num_topics, id2word = dictionary, passes = 30, random_state = 42)
#  印出每个主题中的前15个关键字词
topics_lst = lda.print_topics()
print(topics_lst)

这样模型就完成了,我们透过「pyLDAvis」这个视觉化套件来呈现模型分类结果,详细视觉化程式码在文章最后会提供。

TESG事件雷达:pyLDAvis visialization
TESG事件雷达:pyLDAvis visialization

pyLDAvis的视觉化图表包含两张图表,左侧为主题的分类结果,每一个圆圈代表一个主题,面积越大代表包含的文章数越多,座标轴为 PCA 主成分分析的结果,横轴为第一主成分,纵轴为第二主成分,圆圈之间的距离则为个主题的相似程度。
右侧关键字的统计量,蓝色长条图为全部文章的关键字出现次数,红色为该主题关键字出现次数,透过调整上方的 λ 值能显示出该主题中的独特关键字,λ 越低越独特。

我们可以看到文章大致分类效果算不错,除了第六主题与第七主题有重叠外,其他主题之间分类明确。
然而,LDA 只是以数学的方式对文章进行分群,在数学上或许可以直观的解释分群结果的原因与意义,但却往往与人类的判断不吻合,有时甚至呈现反相关,这些结果以人的角度观察时不一定能用清晰的概念或逻辑解释出来,
并且资料是否有经过妥善的前处理也会很大程度的影响最终分类的结果,套一句IT界的老话「垃圾进,垃圾出」,确实的做好资料前处理才有可能得到理想的结果。然而,前处理往往程序多而繁杂,并且依照任务需求的不同常常会要求须具备一定程度的专业知识、相关经验,不是单单凭借一己之力可以轻易达成的。

TESG 事件雷达透过庞大的资料来源、专业研究员分析以及自然语言处理模型能帮助使用者不再需要具备上述复杂且高门槛的技术与知识就能快速掌握各公司 ESG 的最新消息。

TESG 事件雷达拥有四大优势:

  • 多元事件来源:收录超过20项公开资料来源,关注企业各类ESG事件发生,跟进企业动态。
  • 永续事件分类:以TESG为基准将事件分为三大类16小类,快速了解事件属性及影响层面。
  • 强度标记:快速辨别事件影响幅度。
  • 重点标记:辨识事件性质/裁罚金额。

完整程式码

延伸阅读

相关连结

返回总览页