transformer 资源总结
transformer中的positional encoding(位置编码)
https://blog.csdn.net/Flying_sfeng/article/details/100996524
代码实现部分参考:
https://github.com/DA-southampton/NLP_ability/blob/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86/%E6%96%87%E6%9C%AC%E5%8C%B9%E9%85%8D_%E6%96%87%E6%9C%AC%E7%9B%B8%E4%BC%BC%E5%BA%A6/src/models.py
https://github.com/DA-southampton/NLP_ability/blob/master/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7%90%86/%E6%96%87%E6%9C%AC%E5%8C%B9%E9%85%8D_%E6%96%87%E6%9C%AC%E7%9B%B8%E4%BC%BC%E5%BA%A6/src/models.py
y关键词提取资源总结
- List item
NLP关键词提取方法总结及实现 https://blog.csdn.net/asialee_bird/article/details/96454544
## 正向最大匹配算法
def cut_words(split_sentence,words_dic):
#统计词典中最长的词
max_length = max(len(word) for word in words_dic)
sentence = split_sentence.strip() ## 简单清理一下
#统计序列长度
words_length = len(sentence) ## 在第二个循环的时候,我需要不停的和字典最大长度比较,取最小值作为基线
#存储切分好的词语
cut_word_list = []
while words_length > 0: ## 第二个循环,找到一个之后,循环的去找下一个符合要求的
max_cut_length = min(max_length, words_length)
subSentence = sentence[0 : max_cut_length]
while max_cut_length > 0: ## 第一个循环,迭代找到符号字典的
if subSentence in words_dic:
cut_word_list.append(subSentence)
break
elif max_cut_length == 1:
cut_word_list.append(subSentence)
break
else:
max_cut_length = max_cut_length -1
subSentence = subSentence[0:max_cut_length]
sentence = sentence[max_cut_length:]
words_length = words_length - max_cut_length
return cut_word_list
input_str="今天天气真不错啊,适合出去旅游"
bmm_word_list = cut_words(input_str, words_dic)
print(bmm_word_list)
##逆向最大匹配
def cut_words(raw_sentence,words_dic):
#统计词典中词的最长长度
max_length = max(len(word) for word in words_dic)
sentence = raw_sentence.strip()
#统计序列长度
words_length = len(sentence)
#存储切分出来的词语
cut_word_list = []
#判断是否需要继续切词
while words_length > 0:
max_cut_length = min(max_length, words_length)
subSentence = sentence[-max_cut_length:]
while max_cut_length > 0:
if subSentence in words_dic:
cut_word_list.append(subSentence)
break
elif max_cut_length == 1:
cut_word_list.append(subSentence)
break
else:
max_cut_length = max_cut_length -1
subSentence = subSentence[-max_cut_length:]
sentence = sentence[0:-max_cut_length]
words_length = words_length -max_cut_length
cut_word_list.reverse()
return cut_word_list
总结
参考链接:常见文本相似度计算方法简介 - 李鹏宇的文章 - 知乎
https://zhuanlan.zhihu.com/p/88938220
短文本相似度算法研究 - 刘聪NLP的文章 - 知乎
https://zhuanlan.zhihu.com/p/111414376
现在工业界有哪些比较实用的计算短文本相似度的算法或者模型? - vincent的回答 - 知乎
https://www.zhihu.com/question/342548427/answer/806986596
推荐系统中的深度匹配模型 - 辛俊波的文章 - 知乎
https://zhuanlan.zhihu.com/p/101136699
四种计算文本相似度的方法对比 - 论智的文章 - 知乎
https://zhuanlan.zhihu.com/p/37104535
深度文本匹配发展总结 - xiayto的文章 - 知乎
https://zhuanlan.zhihu.com/p/40741576
常见文本相似度计算方法简介 - 李鹏宇的文章 - 知乎
https://zhuanlan.zhihu.com/p/88938220
参考链接:
https://zhuanlan.zhihu.com/p/48188731
import json
import os
from py2neo import Graph, Node, Relationship
class GoodsKg:
def init(self):
cur = ‘/’.join(os.path.abspath(file).split(’/’)[:-1])
self.data_path = os.path.join(cur, ‘data/goods_info.json’)
self.g = Graph(
host=“127.0.0.1”, # neo4j 搭载服务器的ip地址,ifconfig可获取到
http_port=7474, # neo4j 服务器监听的端口号
user=“lhy”, # 数据库user name,如果没有更改过,应该是neo4j
password=“lhy123”)
return
'''读取数据'''
def read_data(self):
rels_goods = []
rels_brand = []
goods_attrdict = {}
concept_goods = set()
concept_brand = set()
count = 0
for line in open(self.data_path):
count += 1
print(count)
line = line.strip()
data = json.loads(line)
first_class = data['fisrt_class'].replace("'",'')
second_class = data['second_class'].replace("'",'')
third_class = data['third_class'].replace("'",'')
attr = data['attrs']
concept_goods.add(first_class)
concept_goods.add(second_class)
concept_goods.add(third_class)
rels_goods.append('@'.join([second_class, 'is_a', '属于', first_class]))
rels_goods.append('@'.join([third_class, 'is_a', '属于', second_class]))
if attr and '品牌' in attr:
brands = attr['品牌'].split(';')
for brand in brands:
brand = brand.replace("'",'')
concept_brand.add(brand)
rels_brand.append('@'.join([brand, 'sales', '销售', third_class]))
goods_attrdict[third_class] = {name:value for name,value in attr.items() if name != '品牌'}
return concept_brand, concept_goods, rels_goods, rels_brand
'''构建图谱'''
def create_graph(self):
concept_brand, concept_goods, rels_goods, rels_brand = self.read_data()
# print('creating nodes....')
# self.create_node('Product', concept_goods)
# self.create_node('Brand', concept_brand)
# print('creating edges....')
# self.create_edges(rels_goods, 'Product', 'Product')
self.create_edges(rels_brand, 'Brand', 'Product')
return
'''批量建立节点'''
def create_node(self, label, nodes):
pairs = []
bulk_size = 1000
batch = 0
bulk = 0
batch_all = len(nodes)//bulk_size
print(batch_all)
for node_name in nodes:
sql = """CREATE(:%s {name:'%s'})""" % (label, node_name)
pairs.append(sql)
bulk += 1
if bulk % bulk_size == 0 or bulk == batch_all+1:
sqls = '\n'.join(pairs)
self.g.run(sqls)
batch += 1
print(batch*bulk_size,'/', len(nodes), 'finished')
pairs = []
return
'''构造图谱关系边'''
def create_edges(self, rels, start_type, end_type):
batch = 0
count = 0
for rel in set(rels):
count += 1
rel = rel.split('@')
start_name = rel[0]
end_name = rel[3]
rel_type = rel[1]
rel_name = rel[2]
sql = 'match (m:%s), (n:%s) where m.name = "%s" and n.name = "%s" create (m)-[:%s{name:"%s"}]->(n)' %(start_type, end_type, start_name, end_name,rel_type,rel_name)
try:
self.g.run(sql)
except Exception as e:
print(e)
if count%10 == 0:
print(count)
return
if name ==‘main’:
handler = GoodsKg()
handler.create_graph()
#!/usr/bin/env python3
coding: utf-8
File: collect_info.py
Author: lhy<lhy_in_blcu@126.com,https://huangyong.github.io>
Date: 19-3-30
import urllib.request
from urllib.parse import quote_plus
from lxml import etree
import gzip
import chardet
import json
import pymongo
class GoodSchema:
def init(self):
self.conn = pymongo.MongoClient()
return
'''获取搜索页'''
def get_html(self, url):
headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/600.5.17 (KHTML, like Gecko) Version/8.0.5 Safari/600.5.17"}
try:
req = urllib.request.Request(url, headers=headers)
data = urllib.request.urlopen(req).read()
coding = chardet.detect(data)
html = data.decode(coding['encoding'])
except:
req = urllib.request.Request(url, headers=headers)
data = urllib.request.urlopen(req).read()
html = data.decode('gbk')
return html
'''获取详情页'''
def get_detail_html(self, url):
headers = {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"accept-encoding": "gzip, deflate, br",
"accept-language": "en-US,en;q=0.9",
"cache-control": "max-age=0",
"referer": "https://www.jd.com/allSort.aspx",
"upgrade-insecure-requests": 1,
"user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/66.0.3359.181 Chrome/66.0.3359.181 Safari/537.36"
}
try:
req = urllib.request.Request(url, headers=headers)
data = urllib.request.urlopen(req).read()
html = gzip.decompress(data)
coding = chardet.detect(html)
html = html.decode(coding['encoding'])
except Exception as e:
req = urllib.request.Request(url, headers=headers)
data = urllib.request.urlopen(req).read()
html = gzip.decompress(data)
html = html.decode('gbk')
return html
'''根据主页获取数据'''
def home_list(self):
url = 'https://www.jd.com/allSort.aspx'
html = self.get_html(url)
selector = etree.HTML(html)
divs = selector.xpath('//div[@class= "category-item m"]')
for indx, div in enumerate(divs):
first_name = div.xpath('./div[@class="mt"]/h2/span/text()')[0]
second_classes = div.xpath('./div[@class="mc"]/div[@class="items"]/dl')
for dl in second_classes:
second_name = dl.xpath('./dt/a/text()')[0]
third_classes = ['https:' + i for i in dl.xpath('./dd/a/@href')]
third_names = dl.xpath('./dd/a/text()')
for third_name, url in zip(third_names, third_classes):
try:
attr_dict = self.parser_goods(url)
attr_brand = self.collect_brands(url)
attr_dict.update(attr_brand)
data = {}
data['fisrt_class'] = first_name
data['second_class'] = second_name
data['third_class'] = third_name
data['attrs'] = attr_dict
self.conn['goodskg']['data'].insert(data)
print(indx, len(divs), first_name, second_name, third_name)
except Exception as e:
print(e)
return
'''解析商品数据'''
def parser_goods(self, url):
html = self.get_detail_html(url)
selector = etree.HTML(html)
title = selector.xpath('//title/text()')
attr_dict = {}
other_attrs = ''.join([i for i in html.split('\n') if 'other_exts' in i])
other_attr = other_attrs.split('other_exts =[')[-1].split('];')[0]
if other_attr and 'var other_exts ={};' not in other_attr:
for attr in other_attr.split('},'):
if '}' not in attr:
attr = attr + '}'
data = json.loads(attr)
key = data['name']
value = data['value_name']
attr_dict[key] = value
attr_divs = selector.xpath('//div[@class="sl-wrap"]')
for div in attr_divs:
attr_name = div.xpath('./div[@class="sl-key"]/span/text()')[0].replace(':','')
attr_value = ';'.join([i.replace(' ','') for i in div.xpath('./div[@class="sl-value"]/div/ul/li/a/text()')])
attr_dict[attr_name] = attr_value
return attr_dict
'''解析品牌数据'''
def collect_brands(self, url):
attr_dict = {}
brand_url = url + '&sort=sort_rank_asc&trans=1&md=1&my=list_brand'
html = self.get_html(brand_url)
if 'html' in html:
return attr_dict
data = json.loads(html)
brands = []
if 'brands' in data and data['brands'] is not None:
brands = [i['name'] for i in data['brands']]
attr_dict['品牌'] = ';'.join(brands)
return attr_dict
if name == ‘main’:
handler = GoodSchema()
handler.home_list()
关键词的提取,也可以称之为文本标签提取。
比如说,”今天这顿烧烤是真不错啊“,在这句话中,”烧烤“这个词就可以被认为是一个关键词,或者说这个句子的一个标签。
这个标签在一定程度上能够表现出这个句子的含义,比如这个”烧烤“,如果用在文本分类任务中,可以隐含带有”美食“这个类别的信息。
这些标签有些时候也可以用在推荐系统的召回,比如直接按照”烧烤“这个标签做一路召回。
对于关键词的提取一般来说分为抽取式和生成式。其实类比到摘要,其实也是分为抽取式和生成式。
生成式有一个缺点就是有些结果不可控,这其实还挺要命的。
对于抽取式,就是从现有的数据中拿出来词组。最差的结果也就是拿出的单词并不重要,不是我们想要的。
我们的重点是在抽取式提取关键词。
关键词的提取可以分为两个步骤:召回+排序
1.召回
召回就是得到文本中的候选关键词,也就是得到这个句子中有可能是关键词的词汇。
这一步,可以做的方法有很多,比如
我们有积累的关键词词库,在这里直接匹配出来。
一些符合的词性的候选词,比如我挑选出名词作为候选词
还可以基于一些统计特征提出候选词,比如TF-IDF(有些时候统计特征也会用在排序中作为特征)
基于一些规则,比如一个句子出现了人名地名,书名号中词,这些很有可能就是关键词
召回其实是一个很重要的部分,在这一步骤,尽可能的召回有用的词汇。我自己的标准是宁可多不能少。如果多了,无非就是增加了资源消耗,但是少了,可能在排序阶段就是无米之炊了。
2.排序
排序阶段,我们可以将方法大致的分为有监督和无监督的方法
2.1无监督抽取关键词
对于无监督,我们分为基于统计和基于图。基于统计就是TF-IDF和各种变种。基于图最常见的就是TextRank。
关键词提取的一个baseline就是 TF-IDF 提取,这种方法效果已经很好。投入产出比很高,我们一般需要去掉常用的停用词,保留重要的词语。
TF-IDF基于统计,易于实现,但是缺点就是没有考虑词与词,词与文档之间的关系。是割裂的。
另一个baseline就是基于图的TextRank, TextRank 由 PageRank 演变而来。
相比于TF-IDF,TextRank考虑了词与词之间的关系(提取思想就是从窗口之间的词汇关系而来),但是缺点是它针对的是单个文本,而不是整个语料,在词汇量比较少的文本中,也就是短文中,效果会比较差。
随着数据量的积累,我们需要把模型更换到有监督模型加上。一般来说,有监督分为两种,一种是看做序列标注,一种是看做二分类的问题。
2.2有监督之二分类
先说二分类问题,比较简单,就是找到词汇的各种特征,去判断这个词汇是不是这个文本的关键词。
我大概罗列一些可能会用到的特征。
位置特征:
使用位置特征是我们基于文本关键词出现的位置是在大量数据的情况下是有规律可言的,比如微博文本中出现在##符号中部分词汇有很大概率就是文本的一个关键词。
是否出现在开头,是否出现在中间部分,是否出现在末尾,出现的位置(具体是第几个单词);相对于整个文本的位置;是否出现在##符号中…
统计特征:
共现矩阵信息;词频;逆词频;词性;词跨度;关键词所在句子的最大长度/最小长度/平均长度;
向量特征:
关键词词向量和文档向量的相似性
2.3有监督之序列标注
关键词的提取,就是一个典型的序列标注的问题。判断句子中关键词的开头中间结尾的位置。
序列标注最基础的就是HMM和CRF方法,但是特征工程比较复杂。
为了解决特征工程复杂的问题,我们使用深度学习模型序列标注。
关于序列标注,大家可以参考我这个文章内容:
工业级命名体识别经验+代码总结
3.新词发现
还会出现一个问题,如果我们使用二分类判定关键词,上述的过程我们都是基于我们的分词器来做的。有可能会出现一些新词,由于分词错误,不能及时的出现在你的候选词库中,比如”爷青结“。
这个时候,我们需要一个新词发现系统,持续不断的补充到词库中,在召回阶段可以提升召回率。
对于新词发现来说,基操就是从文本的自由程度和凝固程度来判断是否是新词,这样的问题就是阈值不好调整从而导致召回和精准不好平衡。
我们还可以通过别的方法离线挖掘实体词补充道词库中,之前有借鉴美团ner的文章实现了一下,效果还不错,在这里,大家可以参考我这个文章:实体库构建:离线大规模新词实体挖掘
搜索资源总结-持续更新
最近看到一个不错的仓库,搜集搜索相关资源,地址在这里:
https://github.com/frutik/awesome-search#types-of-search
我直接fork了
先列取我看到一些将来可能要看的文章,看到就更新,随时更新
query分析 - 知乎
https://www.zhihu.com/topic/19611289
机器学习(十):损失函数 - DataScienceArt的文章 - 知乎
https://zhuanlan.zhihu.com/p/136047113
大话搜索query理解 - 乔一的文章 - 知乎
https://zhuanlan.zhihu.com/p/111904993
从算法理论到工程实践,AI学习路上你遇到了哪些阻碍、走过哪些弯路? - copytang的回答 - 知乎
https://www.zhihu.com/question/358290436/answer/921061372
现阶段各家公司的广告算法使用的主流模型有哪些? - copytang的回答 - 知乎
https://www.zhihu.com/question/352306163/answer/905601365
国内有哪些比较优秀的搜索引擎? - copytang的回答 - 知乎
https://www.zhihu.com/question/278288679/answer/402251102
医疗搜索中的query词权重算法探索
https://mp.weixin.qq.com/s/JCdzhd1wBKIzDkoqW87OAg
搜索广告之自动化创意
https://mp.weixin.qq.com/s/8CN6Ak9skzxXn_qZntJ0FQ
教你如何动态配置词权重,检索系列文章之HDCT论文笔记 - 刘聪NLP的文章 - 知乎 https://zhuanlan.zhihu.com/p/148211196
深度召回在招聘推荐中的挑战和实践
https://mp.weixin.qq.com/s/_2pPa6v2wgb5ht1j_s4Plg
说一说视频搜索
https://mp.weixin.qq.com/s/Sxuv2H9zJLy04BGXeUiF-g
Embedding搜索能代替文本搜索吗?
https://mp.weixin.qq.com/s/cbIqkGg8IwjnSKpEd54wZg
[第9期] 如何识别用户搜索意图之 Query 扩展
https://mp.weixin.qq.com/s/Zulh3iGXZwDJZ9nH4rbhXQ
搜索引擎技术之Query意图分类
https://mp.weixin.qq.com/s/JUjT1Z9yzyUgKA6cTA3VoQ
在query理解中能ALL IN BERT吗?
https://mp.weixin.qq.com/s/G3dr0toZjHH5hyzFBUU-aQ
NLP技术在搜索中的应用–query理解在搜索中的应用
https://mp.weixin.qq.com/s/ypZu9iO07mH5GskDEOuzfw
Query词权重方法(3) - 基于有监督学习
https://mp.weixin.qq.com/s/1EUSz4_r8-j2wIfsHXq-IQ
Query分析三大法宝(2)- 百度结果
https://mp.weixin.qq.com/s/PPBaBd1lgtTHmtXcB9Lg2A
Query分析三大法宝(3)- 片段粒度
https://mp.weixin.qq.com/s/yL23MxTtAS5fF0fFBDDxWw
搜索Query技术体系|方法论
https://mp.weixin.qq.com/s/8wAJOfTV-BuOuVk9Ul5F4Q
Query 理解和语义召回在知乎搜索中的应用
https://mp.weixin.qq.com/s/MAfK4B2F8sPXRLodXkwnmw
什么叫做倒排索引
在搜索场景中,有一个名词非常的频繁叫做“倒排索引”,今天看了一篇参考资料,大致的了解了一下基本原理,记录下来,以备后用。
首先我们来看,搜索场景是这样的:我有海量文本存储在数据库中,同时每次搜索请求,会有query。
基于海量文本,一个比较直观的想法就是建立正排索引。就是我们的每一个文本(有着自己唯一的一个编号)对应着自己文档中的关键词。
形式如下:
doc1: 关键词1,关键词2,关键词3…
doc2:关键词3,关键词4,关键词5…
doc3:关键词1,关键词9,关键词7…
这样的数据存储结构很不利与搜索。假设我们有一关键词“苹果”,我要找到包含苹果的文档,那么我需要对每个文档进行遍历,才可以,这样下来搜索时长太大。
倒排索引是这样的,使用关键词作为存储结构的key,每个关键词对应着包含这个关键词的doc,形式如下:
关键词1: doc1,doc3…
关键词2: doc1…
关键词3: doc1,doc2…
在这种情况下,如果我们去搜索包含“苹果”这个关键词的文档,只需要对key进行索引就可以。
这就是倒排索引,简单讲就是关键词作为key,包含对应关键词的文档集合作为value。
然后我们在讲一个比较细节的东西,整个倒排索引可以分为三个部分:词典,倒排列表,倒排文件
词典:存储自身编号和指向倒排列表的指针
倒排列表:存储包含某个关键词的所有文档列表以及对应关键词出现的次数位置等信息。
倒排文件:倒排文件是存储倒排索引的物理文件
参考文件链接:
https://www.cnblogs.com/zlslch/p/6440114.html
我觉得这个讲的很好