Step 1: 文本读取
1、读取数据
文本读取:数据分为三份:pos_train,neg_train和test。
首先对pos和neg的data读取,将内容取出来
这里的一个难度是,对于同一组样本,有的以多行的形式展示,还有某些样本被多行之间还有空格。
如以下情况:
处理方法是建立一个save列表,每次遇到文字就储存进去,遇到当前行包含review的行就把这些文字合并成字符串添加到train_pos_comments中。
def process_file(path,list):
save=[]#建立储存列表
f=open(path,"r",encoding="utf-8").readlines()#打开路径
for line in f:#遍历每一行
if "review" in line:#如果当前行的文字含有review
if len(save)!=0:#且save里不为空
list.append("".join(save))#更新一次train_pos_comments
save=[]#清空save
continue#继续下一个循环
if "review" not in line and line.rstrip():#如果当前行是文字
save.append(line.rstrip())#把该行加入save中
process_file(train_pos_file,train_pos_comments)#分别对pos和neg进行处理
process_file(train_neg_file,train_neg_comments)
2、建立label
pos中有5000个,neg有3065个,pos的标签值定为1,neg的标签值定位0,建立与data相同长度的labels
# print(len(train_comments))#5000
# print(len(test_comments))#3065
pos_labels=["1"]*5000
neg_labels=["0"]*3065
3、将pos和neg拼接
将data和label分别拼接起来
'''拼接训练集的pos和neg样本'''
train_set=train_pos_comments+train_neg_comments
train_label=pos_labels+neg_labels
# print(len(train_set))#8065
# print(len(train_label))#8065
4、测试集的文本读取
这里和训练集不同的是每一个回答都自带label,那么在自定义函数中需要返回test_comments和对应的label值
'''测试集文本读取'''
def process_test_file(path):
list=[]#建立空列表储存每一个comment
save=[]#建立save储存每一个子comment
labels=[]#建立labels储存标签值
f=open(path,"r",encoding="utf-8").readlines()#打开路径
for line in f:#遍历每一行
if "review" in line:#如果review出现在里面
if len(save)!=0:#且save的长度不为0
list.append("".join(save))#释放save到list中
save=[]#清空save
if "review" not in line and line.rstrip():#如果当前行有文字
save.append(line.rstrip())#将子文本添加到save中
if 'label="0"' in line:#如果当前行出现0,label append进去
labels.append(0)
if 'label="1"' in line:#如果当前行出现1,label append 进去
labels.append(1)
return list,labels #返回test_comments和test_labels
test_set,test_label=process_test_file(test_cob_file)
Step 2: 简单的可视化
对于每一个用户的回答,统计这个回答的长度,使用Counter返回一个字典,key是回答的长度,values是出现这个长度的回答的数量。
再使用matplotlib画图。
'''简单的可视化:对于训练数据中的正负样本,分别画出一个histogram,x轴是评价的字符数量,y轴是这个长度的比例'''
pos_size=[len(i) for i in train_pos_comments]#统计每个回答的长度
neg_size=[len(j) for j in train_neg_comments]
#统计个数
pos_counter=Counter(pos_size)#返回一个字典
pos_counter=sorted(pos_counter.items(),key=lambda item:item[0],reverse=False)#根据字典的key进行排序
neg_counter=Counter(neg_size)
neg_counter=sorted(neg_counter.items(),key=lambda item:item[0],reverse=False)
# print(pos_counter)
'''
#画表
fig,ax=plt.subplots(1,2)
ax[0].bar([i[0] for i in pos_counter],[j[1]/5000 for j in pos_counter])
ax[1].bar([i[0] for i in neg_counter],[j[1]/3065 for j in neg_counter])
ax[0].set_title=("pos")
ax[0].set_xlabel=("length")
ax[0].set_ylabel=("freq")
ax[1].set_title=("neg")
plt.show()
'''
'''
Step 3:文本处理
- jieba分词处理
- 停用词处理
- 去除标点符号
- 去除数字
'''
文本处理
停用词过滤
去掉特殊符号
去掉数字
'''
#分词
train_set=[jieba.lcut(line) for line in train_set]#使用jieba分词对train_set中的每一个回答进行分词
test_set=[jieba.lcut(line) for line in test_set]#返回的是list of list
# print(test_set)
#停用词表
stopwords=list(stopwords.words("english"))#建立一个停用词表
#2、对停用词过滤
train_set=[ [w for w in line if w not in stopwords] for line in train_set]#返回list of list
test_set=[[w for w in line if w not in stopwords]for line in test_set]
# print(len(train_set2))
#3、去掉非单词符号
pc="[\W+]"#去掉标点符号
train_set=[[re.sub(pc,"",w) for w in line] for line in train_set]#大list代表train_set,小list代表每一个回答的分词结果
test_set=[[re.sub(pc,"",w) for w in line ]for line in test_set]#将出现非单词符号转换成""
# print(test_set)
#4、去掉数字
pc2=r"\d+\.?\d*"
train_set=[[re.sub(pc2,"",w)for w in line]for line in train_set]#去掉数字
test_set=[[re.sub(pc2,"",w)for w in line]for line in test_set]
# print(train_set[36])
#5、把空格的地方去掉
train_set=[[w for w in line if w]for line in train_set]#此时之前是数字和标点符号的地方都是" ”
#过滤掉
test_set=[[w for w in line if w] for line in test_set]
# print(train_set[:20])
Step 4:tf-idf 处理
经过文本处理后返回的是list of list的格式,需要把每个小list合并为一个字符串
#list of list,每一个小list必须返回一个str
train_set_str=[" ".join(line) for line in train_set]
test_set_str=[" ".join(line) for line in test_set]
# print(train_set_str)
再对train_set和test_set做tf-idf处理
'''
feature extraction
使用tf-idf提取特征并写到数组里面
'''
vector=TfidfVectorizer()#建立一个模型
#将每一个label做tf-idf
x_train=vector.fit_transform(train_set_str).toarray()#要返回numpy格式
x_test=vector.transform(test_set_str).toarray()
train_label=[int(one) for one in train_label]#将label的值从str转为int类型
test_label=[int(one) for one in test_label]
可以查看一下set和label的shape
# print(x_train.shape)#(8065, 26558)
# print(x_test.shape)#(2500, 26558)
# print(np.shape(train_label))#(8065,)
# print(np.shape(test_label))#(2500,)
shape一致后需要打乱train_set,为了模型更好的学习要将标签为0和1的值在数组中打乱。
这里用的方法可能比较笨,
具体的是将data和label使用np.hstack拼接到一起,然后使用np.random.shuffle将每一行打乱。再把data和label拆开。
'''将train 和test转换为numpy格式'''
train_label=np.array(train_label).reshape(-1,1)#为了拼接先要reshape
test_label=np.array(test_label)#test_label也要转换为numpy格式,方便后续计算acc
# print(test_label)
#拼接train和test
train_db=np.hstack((x_train,train_label))#拼接data和label
# print(train_db)
#打乱第一行
np.random.shuffle(train_db)#将每一行打乱
# print(train_db)
#分割训练集
x_train=train_db[:,:26558]#前26558是data
y_train=train_db[:,-1]#最后一行是label
print(x_train.shape)
print(y_train.shape)
Step 5:使用logistic 回归进行训练
5.1 超参数调优
这里需要调参的是选择l1还是l2正则,以及超参数c。对于超参数调优使用Gridsearch的方法,但是不要使用训练集的数据。
'''训练模型超参数调优,使用l1还是l2,超参数c选择多少?'''
'''
lr=LogisticRegression(solver="liblinear",random_state=1)#建立一个lr模型
param={"penalty":["l1","l2"],"C":np.logspace(0.01,10,10)}#设定参数的字典,取10个可能的c
clf=GridSearchCV(lr,param_grid=param,cv=5,scoring=make_scorer(f1_score))#建立Gridsearch,cv=5
clf.fit(x_train,y_train)#使用训练集进行调参
print(clf.best_params_)#打印最好的参数 #{'C': 1.023292992280754, 'penalty': 'l2'}
print(clf.best_score_)#打印最好的f1-score #0.8262900218828907#根据f1-score的大小选择合适的C
'''
经过网格搜索,发现C=1.023时,f1-score最大为0.826
此处要注意的是y_train的格式是(8065,)不是(8065,1)
5.2 使用找到的超参数进行模型训练
'''使用训练好的超参数进行预测'''
lr=LogisticRegression(solver="liblinear",penalty="l2",C=1.023,random_state=1)#建立lr
model=lr.fit(x_train,y_train)#建立模型,喂训练集data和label
pred=model.predict(x_test)#使用模型预测x_test
#打印mse
mse=np.average((pred-test_label)**2)# 计算mse,rmse,打印
rmse=np.sqrt(mse)
print("rmse:",rmse)#0.52
#打印准确率
acc=100*np.mean(pred==test_label)#判断预测正确的数量取平均为acc
print("acc:",acc) #72.88
对于准确率的计算,如果pred=test_label,返回1,否则返回0,计算平均值可以得到准确率
也可以使用model.score(x_test,test_label)直接计算准确率。
5.3 Result
经过计算,rmse=0.52,acc=72.88%
准确率很一般,unigram 下的tf-idf只考虑了每个单词 对于pos/neg做的贡献。
5.4 可能的优化方案:
- 1、更好的模型,选择SVM
- 2、使用biagram:加入上下词的关联性
- 3、对文本使用word-embedding
- 3、使用深度学习 RNN,LSTM,可以考虑到上下文的关系。
Step 6:使用svm模型
6.1 svm的超参数调优
可以被调整的:
- 1、核函数类型:运行时间太长,只能选择linear
- 2、C,惩罚因子的参数项
- 3、Gamma:对于高斯核函数,收敛的速度
使用对于超参数量较大的模型,使用贝叶斯优化会更好一点。
6.2 svm模型预测
选定线性核函数,C=" "
# '''使用训练好的超参数进行预测'''
model=SVC(kernel="linear",C="10")
model.fit(x_train,y_train)
# 预测
pred=model.predict(x_test)
#计算准确率
acc=np.mean(pred==test_label)
print("acc:",acc)
print("score_acc:",model.score(x_test,test_label))
#计算rmse
rmse=np.sqrt(np.average((pred-test_label)**2))
print("rmse",rmse)
计算rmse和acc做,acc:70.36%,rmse:0.544
可能超参数没选对,gridsearch用的时间太久了,随便给了个c。
Step 7: 添加n-gram特征
只需要在建立tf-idf 时
给定
vector=TfidfVectorizer(ngram_range=(1,2))
选择unigram和bigram两种情况可能会提高准确率。bigram相当于考虑了每两个词同时出现对于情感分析的影响。
电脑虚拟内存不够跑不出来了,,,,,