NLP Project 2:情感分析,使用tf-idf,Logistic回归和SVM

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相当于考虑了每两个词同时出现对于情感分析的影响。

电脑虚拟内存不够跑不出来了,,,,,

猜你喜欢

转载自blog.csdn.net/weixin_51182518/article/details/113956207