武汉大学国家网络安全学院-社会计算 课程项目1
- 环境:Windows10, python3.8.3
- 安装依赖
- jieba==0.42.1
- paddle==2.1.3
- OpenHowNet==1.0
- anytree==2.4.3
- tqdm==4.31.1
- requests==2.22.0
pip install -r requirements.txt
-
程序入口:
src/frequency.py
: 求词频src/main_sopmi.py
: 通过计算词汇和种子词的SO-PMI判断情感倾向性src/main_sopmi+.py
: 通过计算词汇和种子词的引入词间距的SO-PMI判断情感倾向性src/main_openhownet.py
: 通过HowNet库中词汇和种子词的相关性判断情感倾向性 -
数据:
/data/comment.csv
: 外卖评论数据/data/seeds.csv
: 种子词数据 -
结果:
/data/result_pos{}.csv
&/data/result_neg{}.csv
: 全部正向词和负向词的数据,按极端性从上到下依次排列({}中是应用的方法名称)/data/frequency.csv
: 词频统计,降序排列/data/result{}.csv
: 正向词负向词前50名的数据,词汇出现均大于10次({}中是应用的方法名称) -
其他文件
点互信息(PMI)用来衡量两个事物之间的相关性,两个事物同时出现的概率越大,其相关性越大
-
计算 word1 和 word2 的点互信息(PMI):
-
$$
PMI(word_1,word_2)=log_2[\frac{P(word_1,word_2)}{P(word_1)×P(word_2)}]
$$
其中:
P(word) 是word在出现概率,即word出现的条目占总条目的比
P(word1, word2) 是 word1, word2 共同出现的条目占总条目的比例。即word1, word2 共现概率
-
+1平滑
为了避免因为样本太少而产生0概率事件,所有出现次数均默认比实际出现次数大1
-
计算 word 的情感倾向点互信息(SO-PMI):
$$ SO-PMI(word) = \sum_{seed \in positive_seeds} PMI(seed, word) - \sum_{seed \in negative_seeds} PMI(seed, word) $$
其中: positive_seeds 与 negative_seeds 为正向和负向种子词集合
-
词间距计算:
当两词共现时 $$ d(word_{1}, word_{2}) = min(|index_{word1} - index_{word2}|) $$
其中:
index_word 为词汇在某条评论中的下标
(特别说明,本项目中最大距离设置为100000,远大于最长的评论的长度)
-
SO-PMI:
加入词间距后对SO-PMI的修正
$$ SO-PMI(word, seeds_{i}) = \sum_{seed \in seeds_{i}} \frac{count(comment) * hit(word, seed)}{count(seeds_{i}) * hit(word) * hit(seed) * d(word_{1}, word_{2})} $$
其中:
count(x)为x的数量
hit(word) 为有$word$出现的评论的数目
seed_i 为第i类感情的种子词的集合
-
特殊说明:这种方法的SO-PMI值分别对不同的情感计算,同一个词对每一种情感有一个不同的SO-PMI值,所以可以进一步将情感细化。
HowNet是一个中文语义知识库,这里调用了OpenHowNet库
-
词汇情感倾向
定义:
$$ Polarity(word) = \frac{1}{count(pos_seeds)} * \sum_{seed \in pos_seeds} sim(word, seed) - \frac{1}{count(neg_seeds)} * \sum_{seed \in pos_seeds} sim(word, seed) $$
其中: pos_seeds 与 neg_seeds 为正向与负向种子词的集合
sim(word1,, word2) 为 word1 和 word2 在HowNet中的相似度
-
dataop
该模块包含所有.csv文件的读写,jieba分词,数据排序 的内容
-
globalv
所有全局变量
-
init
初始化
-
frequency
词频统计
-
OHN
OpenHowNet 方法相关内容
def OHN(data: list) -> dict:... def Polarity(word: str, HowNet) -> :...
-
(func) OHN.OHN(data: list) -> dict
该函数处理所有词汇与这组种子在HowNet中的Polarity
@param data list[...{"lable": (int), "review": (list[(str)])}...] 处理后的评论数据 @return word_lib(dict) 在HowNet中存在的词汇及其在该组种子下的极端值(Polarity)
-
(func) OHN.Polarity(word: str, HowNet)
该函数计算特定词汇在该组种子下的极端值
@param word(str) 需要求Polarity的词汇 @param HowNet(HowNetDict) @return Polarity(float)
-
-
SOPMI
SOPMI 方法相关内容
class Word: def __init__(self, word_name: str):... def isInComment(self, cnt_seeds: dict):... def UpdateDist(self, dist_seeds: dict):... def CalSOPMI(self, cnt_comment: float, p_seeds: dict):... def CalSOPMIp(self, cnt_comment:float, cnt_seeds:dict):... def SOPMI(splited_data):... def SOPMI_d(splited_data):... def SpliteSOPMIp(word_sopmi):...
-
(module) SOPMI.Word(word: str)
该模块储存一个词汇的相关统计数据
Word.isInComment(cnt_seeds)
当该词汇在一条评论中时,更新其计数数据
@param cnt_seeds(dict)
Word.UpdateDist(dist_seeds:dict)
当该词汇在一条评论中时,更新其距离数据
@param dist_seeds(dict)
Word.CalSOPMI(cnt_comment: float, p_seeds: dict)
计算该词汇的SOPMI值
@param cnt_comment(float) 所有评论的数量 @param p_seeds(dict) 所有种子词独立出现的概率 @return sopmi(float)
Word.CalSOPMIp(cnt_comment: float, cnt_seeds: dict)
计算该词在考虑距离的情况下的SOPMI值
@param cnt_comment(float) @param cnt_seeds(dict) 所有种子词出现的次数 @return sopmi(float)
-
(func)SOPMI.SOPMI(splited_data:list)
计算所有词汇的SOPMI值
@param splited_data list[..., {"lable": (int), "review": (list[(str)]), ...}] @return word_sopmi dict
-
(func)SOPMI.SOPMI_d
计算所有词汇考虑距离的SOPMI值
@param splited_data list[..., {"lable": (int), "review": (list[(str)])}, ...] @return word_sopmi dict{..., word(str): tuple(sopmi_pos, sopmi_neg), ...}
-
(func)SplitSOPMI(word_sopmi: dict)
将考虑距离的SOPMI方法的结果切分并排序
-
@zbj
-
几种方法的结果正确性分析(我们或许可以认为HowNet的结果和好评差评中的高频词是比较正确的)
-
几种方法的效率
-
数据可视化
-
其他可以水字数的内容
施工中!!!