文本挖掘与分析:Reddit帖子分类案例研究
1. 数据探索
在文本挖掘过程中,数据探索是至关重要的一步。我们对Reddit上的“数据科学”和“权力的游戏”两类帖子进行分析。首先,我们将所有术语进行了分离,但数据量过大,难以判断其是否足够干净以用于实际应用。通过查看单个向量,我们发现了一些问题,比如部分单词未正确拆分,向量中包含许多单字符术语。
单字符术语在某些情况下可能是很好的主题区分器,例如经济文本中会比医学文本包含更多的货币符号($、£、¤),但在大多数情况下,这些单字符术语是无用的。我们先来看一下术语的频率分布:
import nltk
import matplotlib.pyplot as plt
# 假设data是已经处理好的数据
wordfreqs_cat1 = nltk.FreqDist(data['datascience']['all_words'])
plt.hist(wordfreqs_cat1.values(), bins = range(10))
plt.show()
wordfreqs_cat2 = nltk.FreqDist(data['gameofthrones']['all_words'])
plt.hist(wordfreqs_cat2.values(), bins = range(20))
plt.show()
通过绘制频率分布直方图,我们很快发现大部分术语只在单个文档中出现。这些只出现一次的术语被称为“hapaxes”,从模型的角度来看,它们是无用的,因为单个特征的出现不足以构建可靠的模型。去除这些hapaxes可以显著缩小数据量,而不会损害最终的模型。我们可以查看这些单出现次数的术语:
print(wordfreqs_cat1.hapaxes())
print(wordfreqs_cat2.hapaxes())
许多这些术语是有用单词的错误拼写,比如“Jaimie”应为“Jaime(兰尼斯特)”,“Milisandre”应为“Melisandre”等。一个针对《权力的游戏》的专业词库结合模糊搜索算法可以帮助我们找到并替换这些拼写错误。这也证明了文本挖掘中的数据清理可以无限进行下去,关键是要在努力和回报之间取得平衡。
接下来,我们查看最频繁出现的单词:
print(wordfreqs_cat1.most_common(20))
print(wordfreqs_cat2.most_common(20))
结果显示,一些常见单词似乎特定于它们的主题,如“data”“science”和“season”可能是很好的区分器。同时,我们注意到大量的单字符术语,如“.”和“,”,我们将去除这些术语。
2. 数据准备调整
基于上述数据探索,我们对数据准备脚本进行了修订。其中一个重要的调整是对术语进行词干提取。以下是使用“snowball stemming”的简单词干提取算法:
import nltk
stemmer = nltk.SnowballStemmer("english")
def wordStemmer(wordrow):
stemmed = [stemmer.stem(word) for word in wordrow]
return stemmed
manual_stopwords = [',','.',')',',','(','m',"'m","n't",'e.g',"'ve",'s','#','/',
'``',"'s","''",'!','r',']','=','[','s','&','%','*','...','1','2','3',
'4','5','6','7','8','9','10','--',"''",';','-',':']
def data_processing(sql,manual_stopwords):
# 假设c是已经建立好的SQLite连接游标
c.execute(sql)
data = {'wordMatrix':[],'all_words':[]}
interWordMatrix = []
interWordList = []
row = c.fetchone()
while row is not None:
tokenizer = nltk.tokenize.RegexpTokenizer(r'\w+|[^\w\s]+')
wordrow = tokenizer.tokenize(row[0]+" "+row[1])
wordrow_lowercased = lowerCaseArray(wordrow) # 假设lowerCaseArray函数已定义
wordrow_nostopwords = wordFilter(stopwords,wordrow_lowercased) # 假设stopwords和wordFilter函数已定义
wordrow_nostopwords = wordFilter(manual_stopwords,wordrow_nostopwords)
wordrow_stemmed = wordStemmer(wordrow_nostopwords)
interWordList.extend(wordrow_stemmed)
interWordMatrix.append(wordrow_stemmed)
row = c.fetchone()
wordfreqs = nltk.FreqDist(interWordList)
hapaxes = wordfreqs.hapaxes()
for wordvector in interWordMatrix:
wordvector_nohapexes = wordFilter(hapaxes,wordvector)
data['wordMatrix'].append(wordvector_nohapexes)
data['all_words'].extend(wordvector_nohapexes)
return data
# 假设subreddits是包含感兴趣的主题列表
for subject in subreddits:
data[subject] = data_processing(sql='''SELECT
topicTitle,topicText,topicCategory FROM topics
WHERE topicCategory = '''+"'"+subject+"'",
manual_stopwords=manual_stopwords)
与之前的数据处理函数相比,我们的分词器现在是一个正则表达式分词器,它将文本切割成单词,去除了特殊字符和标点符号。我们还应用了词干提取器,并去除了额外的停用词。最后,去除所有的hapaxes。再次运行数据准备后,我们发现数据质量有了显著提高。
以下是数据准备的流程图:
graph TD;
A[开始] --> B[执行SQL查询];
B --> C[初始化数据结构];
C --> D[逐行处理数据];
D --> E[分词];
E --> F[转换为小写];
F --> G[去除停用词];
G --> H[词干提取];
H --> I[更新临时词列表和矩阵];
I --> J{是否还有数据};
J -- 是 --> D;
J -- 否 --> K[计算词频];
K --> L[获取hapaxes];
L --> M[去除hapaxes];
M --> N[更新最终数据结构];
N --> O[结束];
3. 数据转换与拆分
数据清理完成后,我们需要进行一些数据转换,将数据转换为词袋格式。具体步骤如下:
1. 标记所有数据,并为每个类别创建一个包含100个观察值的保留样本。
import random
from collections import OrderedDict
holdoutLength = 100
labeled_data1 = [(word, 'datascience') for word in data['datascience']['wordMatrix'][holdoutLength:]]
labeled_data2 = [(word, 'gameofthrones') for word in data['gameofthrones']['wordMatrix'][holdoutLength:]]
labeled_data = []
labeled_data.extend(labeled_data1)
labeled_data.extend(labeled_data2)
holdout_data = data['datascience']['wordMatrix'][:holdoutLength]
holdout_data.extend(data['gameofthrones']['wordMatrix'][:holdoutLength])
holdout_data_labels = [('datascience') for _ in range(holdoutLength)] + [('gameofthrones') for _ in range(holdoutLength)]
data['datascience']['all_words_dedup'] = list(OrderedDict.fromkeys(data['datascience']['all_words']))
data['gameofthrones']['all_words_dedup'] = list(OrderedDict.fromkeys(data['gameofthrones']['all_words']))
all_words = []
all_words.extend(data['datascience']['all_words_dedup'])
all_words.extend(data['gameofthrones']['all_words_dedup'])
all_words_dedup = list(OrderedDict.fromkeys(all_words))
prepared_data = [({word: (word in x[0]) for word in all_words_dedup}, x[1]) for x in labeled_data]
prepared_holdout_data = [({word: (word in x[0]) for word in all_words_dedup}) for x in holdout_data]
random.shuffle(prepared_data)
train_size = int(len(prepared_data) * 0.75)
train = prepared_data[:train_size]
test = prepared_data[train_size:]
- 保留样本将用于最终测试模型并创建混淆矩阵,混淆矩阵可以检查模型在未见过的数据上的表现,显示正确和错误分类的观察值数量。
- 在创建训练和测试数据之前,我们需要将数据转换为词袋格式,每个术语根据其在特定帖子中的存在情况被赋予“True”或“False”标签,对未标记的保留样本也进行同样的处理。
以下是数据转换和拆分的步骤总结表格:
| 步骤 | 操作 |
| ---- | ---- |
| 1 | 标记数据并创建保留样本 |
| 2 | 去重所有术语 |
| 3 | 将数据转换为词袋格式 |
| 4 | 打乱数据 |
| 5 | 划分训练集和测试集 |
现在我们已经准备好进行数据分析了。
文本挖掘与分析:Reddit帖子分类案例研究
4. 数据分析
为了对Reddit上“数据科学”和“权力的游戏”两类帖子进行分类分析,我们选择了两种分类算法:朴素贝叶斯和决策树。
4.1 朴素贝叶斯分类器
首先,我们使用NLTK库中的朴素贝叶斯分类器进行训练,并使用测试数据评估其整体准确性。
import nltk
classifier = nltk.NaiveBayesClassifier.train(train)
accuracy = nltk.classify.accuracy(classifier, test)
print(f"朴素贝叶斯分类器在测试数据上的准确率: {accuracy}")
测试数据上的准确率估计超过90%,但为了更全面地评估模型,我们使用保留样本进行最终测试,并创建混淆矩阵。
classified_data = classifier.classify_many(prepared_holdout_data)
cm = nltk.ConfusionMatrix(holdout_data_labels, classified_data)
print(cm)
混淆矩阵显示,在200个观察值的保留样本中,有28个被错误分类,准确率为86%,这表明模型比随机分配(准确率50%)表现更好。我们还查看了模型中最具信息量的特征:
print(classifier.show_most_informative_features(20))
结果显示,“data”等词对数据科学类别有重要指示作用,而“scene”“season”等词则表明帖子可能属于“权力的游戏”类别。
以下是朴素贝叶斯分类器评估的流程图:
graph TD;
A[训练朴素贝叶斯分类器] --> B[评估测试数据准确率];
B --> C[使用保留样本分类];
C --> D[创建混淆矩阵];
D --> E[查看最具信息量的特征];
E --> F[结束];
4.2 决策树分类器
接下来,我们使用决策树分类器进行训练和评估。
classifier2 = nltk.DecisionTreeClassifier.train(train)
accuracy2 = nltk.classify.accuracy(classifier2, test)
print(f"决策树分类器在测试数据上的准确率: {accuracy2}")
classified_data2 = classifier2.classify_many(prepared_holdout_data)
cm2 = nltk.ConfusionMatrix(holdout_data_labels, classified_data2)
print(cm2)
测试数据上的准确率为93%,但在保留样本的混淆矩阵中,决策树模型在处理“数据科学”帖子时表现不佳,更倾向于将帖子分类为“权力的游戏”类别。我们查看决策树的伪代码:
print(classifier2.pseudocode(depth=4))
决策树模型是树状结构,从根节点开始,依次检查术语,如先检查“data”,若存在则分类为数据科学,若不存在则继续检查其他术语。决策树表现不佳的一个可能原因是缺乏剪枝,剪枝可以减少叶子节点数量,避免过拟合。
以下是决策树分类器评估的表格:
| 评估指标 | 测试数据 | 保留样本 |
| ---- | ---- | ---- |
| 准确率 | 93% | 需根据混淆矩阵计算 |
| 错误分类情况 | - | 倾向于将帖子分类为“权力的游戏” |
5. 结果呈现与自动化
最后一步是将我们的发现呈现给他人或转化为有用的应用程序。
5.1 朴素贝叶斯模型的可视化
我们可以使用力导向图来表示朴素贝叶斯模型,其中气泡和链接的大小表示单词与“数据科学”或“权力的游戏”子版块的关联强度。虽然静态图有一定局限性,但可以打开“forceGraph.html”文件,享受d3.js力导向图的交互效果。代码示例可参考:http://bl.ocks.org/mbostock/4062045。
5.2 决策树模型的可视化
对于决策树模型,我们可以使用太阳爆发图进行可视化。通过点击圆圈段可以进行缩放,代码示例可参考:http://bl.ocks.org/metmajer/5480307。
以下是结果呈现的步骤列表:
1. 选择可视化方式,如力导向图或太阳爆发图。
2. 根据相应的代码示例进行实现。
3. 分享可视化结果,传达分析发现。
通过以上步骤,我们完成了对Reddit帖子的分类分析,并通过可视化方式将结果呈现出来,为进一步的应用和决策提供了有价值的信息。
超级会员免费看
979

被折叠的 条评论
为什么被折叠?



