最近在做微博的评论情感分析,本来想用Tensorflow的RNN来实现,但网上一直找不到好的训练集,在CSDN上买的几万条的微博情感标注集效果也不好,模型对训练集的准确率很高,但放到实际预测中效果很差。
在暂时没有时间自己做标注的情况下,只能先考虑一些现有的工具了,之前用过百度AI的自然语言处理,凑合能用,个人感觉他们的训练集应该量是比较大的,能适应的场景比较多。实际使用后还没有发现很离谱的打标打错的情况,所以记录一下供大家参考,使用的是Python3.6+Requests库。
准备工作
首先要先在百度AI控制台申请一下应用。
创建应用后保存自己的API key和Secret key,要用到这两个字段去获取AccessToken,然后再用AccessToken访问API。
获取AccessToken
虽然百度API的技术文档里有介绍,但用的是python2.7版本的,而且是urllib库。转换成Requests库更方便,代码如下:
# 获取AccessToken类
class GetAccessToken:
def __init__(self):
# 【修改点1】输入自己在百度AI控制台申请应用后获得的AK和SK
self.AK = ''
self.SK = ''
self.token_url = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id='+self.AK+'&client_secret='+self.SK
self.headers = {'Content-Type':'application/json; charset=UTF-8'}
def get_access_token(self):
r = requests.get(self.token_url,headers = self.headers)
if r.text:
tokenkey = json.loads(r.text)['access_token']
print('get token success')
return tokenkey
else:
print('get token fail')
return ''
将之前申请的API key和Secret key填入字段即可获得AccessToken。
调用API接口
由于是穷人,只能使用普通权限,QPS限制为5,也就是最多这个接口每秒能处理5次请求,超过就会返回错误。但实际的请求速度还是跟网速和电脑配置有关,比如设置了每个0.1秒请求一次,但实际发送请求时有时候是0.1S发送,有时候是0.3S发送,但如果连续多次0.1S发送请求就会报错,因此在调用时考虑每次出现错误就增加一点等待时间,而一段时间没有报错就减少一点等待时间,保持自己能最大化利用免费接口的请求量(这就是穷人的心机吗...)。代码解释见注释,直接使用需修改两个地方1.更改待分析文本所在文件夹地址;2.根据自己文本的格式,修改读取文件的方式
# 调用API类
class SentimentBaidu:
def __init__(self,tp):
# 调用自己需要使用的API,这里使用情感分析API作为示例,其他接口查询百度AI说明文档后替换 + 前的URL
self.HOST = 'https://aip.baidubce.com/rpc/2.0/nlp/v1/sentiment_classify' + '?charset=UTF-8&access_token='
self.headers = {'Content-Type': 'application/json','Connection': 'close'}
self.textpath = tp
self.commentcomment = []
self.count = 0
# 速度设置,对应百度AI的QPS限制,由于request发送请求的速度跟网速、设备性能有关,所以虽然限制是5QPS,但可以把请求的间隔写短点,这里采用的是最快0.06s
self.speedlimit = 0.06
# 初始速度,后续实际请求速度程序自动根据错误调整
self.sleepdt = 0.08
self.errorwaittime = 0.5
self.qpserror = 0
self.qpserrorindex = 0
self.errorallcount = 0
# 调用百度API获得情感分析结果方法
def get_content_sentiments(self,text,at):
raw = {'text': text}
data = json.dumps(raw).encode('gbk')
url = self.HOST+at
try:
if self.count - self.qpserrorindex > 500:
if self.sleepdt > self.speedlimit:
self.sleepdt -= 0.001
print('speed up, current speed:',
self.sleepdt)
self.qpserrorindex = self.count
time.sleep(self.sleepdt)
r = requests.post(url=url, data=data, headers=self.headers)
if 'error_code' in r.text:
error = r.json()['error_code']
print('error_code',error)
if error == 18:
self.errorallcount += 1
self.qpserror += 1
self.qpserrorindex = self.count
self.sleepdt += 0.001
print('current qps error count = ', self.qpserror, 'speed down, current speed:', self.sleepdt, self.errorallcount)
time.sleep(self.errorwaittime)
content = r.json()
except Exception as e:
self.errorallcount += 1
time.sleep(self.errorwaittime)
return
try:
if content['items']:
contentposprob = content['items'][0]['positive_prob']
contentnegprob = content['items'][0]['negative_prob']
contentconfi = content['items'][0]['confidence']
contentsenti = content['items'][0]['sentiment']
temp = [contentposprob,contentnegprob,contentconfi,contentsenti]
return temp
except KeyError as e:
self.errorallcount += 1
print('error reason:',content)
time.sleep(self.errorwaittime)
return
# 使用pandas读取所有待分析文本
def get_comment_ori(self,fp):
fpath = fp
fpl = os.listdir(fpath)
contentall = []
for file in fpl:
fd = fpath + '/' + file
print('reading',fd)
temp = pd.read_csv(fd)
contentall.append(temp)
contentalldf = pd.concat(contentall, ignore_index=True, sort=False)
print('comment get:',contentalldf.shape[0])
return contentalldf
# 主程序
def run(self):
requests.adapters.DEFAULT_RETRIES = 5
ATclass = GetAccessToken()
AT = ATclass.get_access_token()
print('progress start current speed = ', self.sleepdt)
# 【修改点3】以下4行为获取文本,可选择自己常用的方式,将所有文本待分析文本放到一个iterator中,这里使用的pandas读取文本
contentalldf = self.get_comment_ori(self.textpath)
commentcontent = contentalldf['commentContent']
commentcontent = pd.DataFrame(commentcontent)
commentcontent.columns = ['comment']
# 如果在调用接口前想先对文本符号、表情等信息进行清理,可调用clean_comment函数,通过pandas的apply快速处理所有文本
# commentcontent['comment'] = commentcontent['comment'].apply(self.clean_comment)
for comment in commentcontent['comment']:
if comment:
self.count += 1
if self.count % 100 == 0:
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())),'正在处理第{0}条评论'.format(self.count))
commentsenti = self.get_content_sentiments(comment,AT)
if commentsenti is not None:
commentbatch = [comment]+commentsenti
self.commentcomment.append(commentbatch)
if self.count % 100 == 0:
commentsentidf = pd.DataFrame(self.commentcomment,
columns=['comment', 'contentposprob', 'contentnegprob',
'contentconfi', 'contentsenti'])
fpath = self.textpath + '/cpbaidu.csv'
if os.path.exists(fpath):
commentsentidf.to_csv(fpath, mode='a', encoding='utf-8-sig', index=False, header=False)
else:
commentsentidf.to_csv(fpath, encoding='utf-8-sig', index=False)
# print('write to path',fpath,'write num:',commentsentidf.shape[0])
self.commentcomment = []
print('finished progress')
# 文本清理方法,可选是否剔除文本内的符号、空格、emoji的剔除
def clean_comment(self,text):
emoji = re.compile(u'['
u'\U0001F300-\U0001F64F'
u'\U0001F680-\U0001F6FF'
u'\u2600-\u2B55]+',
re.UNICODE)
text = re.sub("[\s+\.\!\/_,$%^*(+\"\']+|[+——!,。?、~@#¥%……&*()::]+", "", text)
text = re.sub(emoji,'',text)
return text
if __name__=='__main__':
tp = '' # 【修改点2】修改为自己的文本存储的文件夹位置
runner = SentimentBaidu(tp)
runner.run()
调用效果
实际效果打标结果只能说勉强能用,而且由于之前没有做停用词去除,所以很多微博名字也被放到文本中进行分析了,所以最好在将文本提交API之前先对文本进行清理,去掉停用词和符号等内容,另外如果纯表情的微博回复调用接口会直接报空,也可以提前清除。打标结果如下,后面字段分别为,积极概率,消极概率,判断信心,情感分类(0消极 1中性 2积极)
完整项目见:https://github.com/pandasgb/SentimentAnalysisWithBaiduAI