赞
踩
先来观察下数据,训练集和测试集分别存储在当前目录下的train.csv和test.csv中
- train_data = pd.read_csv('train.csv', lineterminator='\n')
- test_data = pd.read_csv('test.csv', lineterminator='\n')
- train_data.head(10)
输出:
乍一看这个评论是什么鬼……咸鱼不懂,就只能直接使用jieba对其进行分词。
当然了,一些明显无意义的词我们还是应该对其进行删除,如数字、标点符号等等(具体哪些我是在后面统计词频时发现的)。
还有,注意到review字段的内容首字母都是大写的,中间也可能出现首字母大写的词语,所以构造词袋时,要将这些单词统一转换成小写,以免重复计算,像我这样的咸鱼,就干脆直接都统一转换成小写存入set进行去重。
- #构建词袋
- def getBagsOfWord(data):
- #使用python内置对象set,通过其或、差运算进行添加、删除词汇
- vocabSet = set()
- for rec in data['review']:
- #对每一条review进行jieba分词,并且统一小写化存入,空串返回None
- vocabSet |= set(list(map(lambda x:x.strip().lower() if len(x.strip().lower()) > 0 else None, jieba.cut(rec))))
- #删除空串
- vocabSet.remove(None)
-
- #删除指定的字符集
- vocabSet -= set(rmSignal)
-
- #删除数字
- vocabSet = filter(lambda x:not x.isdigit(), vocabSet)
- return vocabSet
-
- #要删除的字符集
- rmSignal = ['.', '?', '!', ':', '-', '+', '/', '"', ',']
-
- #构建训练集的词袋
- vocabList = list(getBagsOfWord(train_data))#list(getBagsOfWord(train_data) | getBagsOfWord(test_data))
-
- #输出词袋长度,以及前10个词汇
- print(len(vocabList), vocabList[:10])
输出:
这里只统计了训练集的词袋,共有17582个,从词袋的部分内容我们可以看到,表情也被保留做词袋的内容了(咸鱼不懂怎么删除QAQ)。
然后我们再统计下词频,类似“的”,“地”,“得”等这些没有情感影响但经常使用的词,可以很容易的得出,当然,一些符号也会被统计出来。
- #根据词袋计算词频
- def getCountWords(data, vocabList):
- cntWords = [0] * len(vocabList)
- for rec in data['review']:
- sentence = list(map(lambda x:x.strip().lower() if len(x.strip().lower()) > 0 else None, jieba.cut(rec)))
- for wd in sentence:
- if wd in vocabList:
- cntWords[vocabList.index(wd)] += 1
- cntWords = [(cntWords[i], vocabList[i]) for i in range(len(vocabList))]
- return cntWords
-
-
- cntWords = getCountWords(train_data, vocabList)
-
- #将词频列表按词频大小排序
- cntWords = sorted(cntWords, key=lambda x:x[0])
当然,这里可以使用TF-IDF计算出最重要的词汇(效果可能会好很多,而且更加简单粗暴!)
同时sklearn中,也封装好了一个可以获取关键词的接口CountVectorizer,具体用法这里不再介绍。
from sklearn.feature_extraction.text import CountVectorizer
使用matplotlib进行可视化分析,这里使用的条形统计图。
注意matplotlib可能会中文乱码,需要设置下字体参数。
- #绘制出现次数最高的前topK个词语
- def plotTopFrequeceWord(cntWords, topK=10):
- cntWords = sorted(cntWords, key=lambda x:x[0])[-topK:]
- print(cntWords)
-
- plt.bar(list(range(topK)), [x[0] for x in cntWords], align = 'center',color='steelblue', alpha = 0.8)
- plt.ylabel('词频')
- plt.xlabel('词语')
- plt.title('出现最多的前%s个词'%(topK))
- plt.xticks(list(range(topK)), [x[1] for x in cntWords])
-
- # 为每个条形图添加数值标签
- for x,y in enumerate(cntWords):
- plt.text(x, y[0], '%s' %(y[0]), ha='center')
-
-
- # 中文乱码的处理
- plt.rcParams['font.sans-serif'] =['SimHei']
- plt.rcParams['axes.unicode_minus'] = False
-
- plotTopFrequeceWord(cntWords, 20)
输出:
既然有了词汇表,那么接下来我们所要处理的评论,就都应该转换成其对应的词向量,便于后面的计算。
- #将语句转换成指定词汇表的词向量
- def word2Vec(vocabList, ip):
- recVec = [0] * len(vocabList)
- ip = jieba.cut(ip)
-
- for word in ip:
- word = word.strip().lower()
- #不在词汇表的不处理
- if word not in vocabList:
- continue
-
- #前期进行测试,所有输出了不在词汇表的单词
- if word in vocabList:
- recVec[vocabList.index(word)] = 1
- else:
- print("{0} is not in vocabList!".format(word))
- return recVec
然后将从csv文件读取到的数据,通过word2Vec函数,将每条评论转换成对应的词向量,最终转换成矩阵,以便于运算。
- #将dataFrame数据变成矩阵
- def makeDataMat(data, vocabList):
- data_mat = []
- for rec, label in zip(data['review'], data['label']):
- data_mat.append(word2Vec(vocabList, rec) + [label])
-
- return np.mat(data_mat, dtype=np.double)
-
-
- #训练集
- train_mat = makeDataMat(train_data, vocabList)
-
- #测试集
- test_mat = makeDataMat(test_data, vocabList)
-
- #观察矩阵参数
- print(train_mat.shape, test_mat.shape)
-
- #观察矩阵稀疏情况
- print("sparsity of train_mat is {0}%\n".format(100. - np.sum(train_mat[:100, :] / train_mat.shape[1])))
- print("sparsity of train_mat is {0}%\n".format(100. - np.sum(test_mat[:10, :] / test_mat.shape[1])))
输出:
朴素贝叶斯是可以作为一个简单又好用的垃圾分类器,算法思想挺简单的,这里不再介绍原理,大家可以去百度搜搜。
- #训练二分类朴素贝叶斯,返回3个参数
- #p0Vect表示在类别0中,各个词汇出现的概率向量
- #p1Vect表示在类别1中,各个词汇出现的概率向量
- #pAbusive表示训练集中,类别1的概率
-
- #trainMatrix表示训练集的词汇矩阵
- #trainCategory表示各个样本的类别
- def trainNB0(trainMatrix, trainCategory):
- numTrainDocs = len(trainMatrix)
- numWords = trainMatrix.shape[1]
-
- #二分类问题,可直接对类别向量进行求和,即类别1的个数
- pAbusive = sum(trainCategory) / numTrainDocs
- p0Num = np.ones(numWords).reshape(1, -1)
- p1Num = np.ones(numWords).reshape(1, -1)
-
- #为避免计算概率时,分母为0,
- p0Denom = 2.
- p1Denom = 2.
-
-
- for i in range(numTrainDocs):
- if trainCategory[i] == 1:
- p1Num += trainMatrix[i]
- p1Denom += sum(trainMatrix[i])
- else:
- p0Num += trainMatrix[i]
- p0Denom += sum(trainMatrix[i])
- #将频率转换成概率
- p1Vect = p1Num / p1Denom
- p0Vect = p0Num / p0Denom
- return p0Vect, p1Vect, pAbusive
-
- p0Vect, p1Vect, pAbusive = trainNB0(train_mat[:, :-1], train_mat[:, -1])
这里因为在把词向量中每个元素相乘时,可能会导致结果很小,所以这里可以对最终的概率表达式进行求对数,这样就可以化乘为加。
但同样的,最后我们需要的是一个概率,所以我计算出数据为类别1的概率以及0的概率后,计算类别1的概率所占的比值,作为最终结果。
- #预测数据
- #vec2Classify表示待预测的数据
- #p0Vec, p1Vec, pClass1分别对应着之前朴素贝叶斯模型的参数
-
- def predictNB(vec2Classify, p0Vec, p1Vec, pClass1):
- p1 = np.sum(vec2Classify * p1Vec) + np.log(pClass1)
- p0 = np.sum(vec2Classify * p0Vec) + np.log(1-pClass1)
-
- #因为概率是小于1的,取对数后,结果小于0,所以为正类的概率要用1减去类别1的占比
- return p1 / (p1 + p0) if p1 > 0 else 1-p1 / (p1 + p0)
至此,所以技术问题已经解决,提交后score为0.81415231,咸鱼水平有限,大佬们勿喷。
看到一些大佬的高分策略,都是使用了一些框架,咳咳,我还是抓紧时间补补吧QAQ。
数据集和代码可以访问咸鱼的Github
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。