0%

知识图谱:语义知识形式化表达的框架,节点表示本体,边表示关系.

知识图谱建设对众多NLP基础技术依赖很大,是一个系统工程,需要整体考虑.
所以梳理一下思路对整体认识有帮助.
但,这是一个更偏工程技术的挑战,而且成本也不小,所以既考规划也考技术,整理思路可能只对思路有帮助,实际建设过程却非常有可能遇到七七八八的问题.

需求

这个是第一位需要考虑的,这个会对大范围进行限定,因为会从“我要建知识图谱” => “我要建xxxx的知识图谱”.
需求会明确所面临的是开放领域还是专业领域,这个区别会造成数据来源的不同,以及对结果的要求高低也会不一样.
开放领域的数据可能一般通过从互联网爬取获得.
专业领域一般都有自己的数据积累,更新途径.

专业领域的知识图谱可能原本就会有一些相关应用,只是不叫知识图谱罢了,所以其要求也会更高.

技术

实体提取、关系提取、图谱存储、检索等

实体提取

  • 词典匹配 | 有了词典后就很简单快速,关键是如果要更新的就需要维护词典
  • 统计方法(NER) | 将NER问题转换为序列标注问题,一共那么几个标记,根据训练数据分布,计算当前语序下每个标记的概率,选概率最大的作为标记,这可以识别未登录词

关系提取

如果全部的关系集合预先指定好的话(比如谓词集合),任务也可视为当两个实体同时出现时实体间关系的分类问题.如果不当成分类问题可能就需要当成一个生成问题,根据句法分析后的依存关系生成实体间的关系.

基于依存关系生成主要基于句法分析和依存模版进行关系提取,可以看看EntityRelation的Python工具包实现,及对应的论文Chinese Open Relation Extraction and Knowledge Base
Establishment

关系分类大体可以分为无监督方法(基于模版),监督方法(基于特征/基于核函数/基于深度学习)
基于模版根据固定的语法格式提取实体间的关系,模版的生成可以是专家根据语言规则生成也可以用统计方法生成(利用搜索引擎等其它外部工具验证模版).
人工模版判断实体的上下位关系的准确率比较高,是因为这个模版所涉及到的表达形式比较固定,但对于其它关系可能就比较困难.
其他关系可以考虑使用统计生成模版,步骤是选择一个关系涉及到的实体对提交到搜索引擎,保留结果中包含实体对的的最长子串,这样会可能会得到多个模版,计算每个模版的置信度(只提交问题实体到搜索引擎,取包含实体的结果作为分母,契合某个模版的结果作为其模版的分子计算置信度),可以根据置信度阀值或者TopN提取模版.

基于特征就是根据标记数据训练分类模型,分类模型有很多可选,特征一般可以考虑(词汇相关/数值相关/类型相关/依存相关等等)
基于核函数利用空间转换计算相似度进行关系分类,方法复杂度比较高
基于深度学习从训练数据利用端到端对关系进行打分排序,进行关系分类

图谱存储

关系型数据库/图数据库
前者更新快,查询慢
后者查询快,更新慢

但是前者的使用成本可能会小一些,后者由于是一个相对新,可能需要趟更多的坑.包括但不限于图查询,图索引,子图优化等

检索

SQL/SPARQL

应用

包括知识推理在内应该属于应用范围,
其它应用领域可能有:搜索引擎,智能问答,推荐系统等

起源

yuange1975 在微博发布了一个趣味问题,看到觉得挺有意思,试着做了一下,相比原需求,多写了一个贷款数据生成的类,便于测试.
需求介绍及算法思路都在代码中注释,最终正确结果等yuange公布.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
"""
LG
ourantech@163.com
2019-09-04
这是yuange1975的一个兴趣题。just for fun。

需求:
@yuange1975
https://weibo.com/2246379231/I5fgWcrFF?from=page_1005052246379231_profile&wvr=6&mod=weibotime&type=comment

最近在微信的微粒贷里借钱了,发现还款还是一个好的算法征解题。
问题,在微信微粒贷里不同时间借款若干笔,现在有一笔资金,需要还款,求通用的还款算法(还哪些笔欠款)?

已知条件有:
1、微粒贷不支持单笔借款部分还款,一笔还款必须还完。如果支持部分还款,那就问题比较简单了。
2、借款总额度还够,微粒贷还可以马上借款出来。
3、一个还款周期里,不收复利,每天按本金按固定利率算利息。
4、现金的收益利率比借款利率低,否者就不去还款了。
5、假定所有的借款下一还款时间都一样。(微粒贷不同借款还款周期都是每月固定的某一天,如果某笔借款到下一个月的还款时间小于一个月,好像这笔借款第一次还款就会到再下一个月)。这个假设简化一点点。
6、微粒贷每笔借款有最高限额4万,这个没什么影响。
7、微粒贷利率每天万2。
8、现金理财收益每天万1。
9、借款、还款本金必须是100的整数倍。

"""
import random

LOAN_RATIO = 0.0002 # 贷款利率
CASH_RATIO = 0.0001 # 现金收益


class Repay(object):
"""
还款算法:
每需要还一笔就迭代一轮看应该最先还哪一笔
迭代的内容是 计算还款收益,还款收益的主要分为两种情况:
一是如果手上的金额大于该笔待还款金额,则 还款收益=贷款剩余本金利息-待还款金额现金收益;
二是如果手上的金额小于待还款金额,则 还款收益=贷款剩余本金利息-与手上现金和大于待还款金额的满足借款规则的最小贷款金额利息-待还款金额现金收益;且增加一笔贷款。
选择还款收益为正且最大的那一笔贷款最优先还款
如果没有为正的还款收益贷款,则不还款,停止迭代
如果手上没有现金,则停止迭代
如果没有剩余的贷款,则停止迭代
"""
def __init__(self, loans, cash):
self.loans = loans
self.cash = cash
self.update_loans()

def plan(self):
max_repay_income = self.get_max_repay_income()
while self.loans and max_repay_income > 0:
self.pay()
self.update_loans()
max_repay_income = self.get_max_repay_income()
return self.loans, self.cash

def update_loans(self):
temp = []
for m, n, i, j, k in self.loans:
if self.cash >= j:
temp.append([m, n, i, j, round(self.repay_income_1(i, j), 3)])
else:
min_aount = self.min_loan_amount(j, self.cash)
temp.append([m, n, i, j, round(self.repay_income_2(i, j, min_aount), 3)])
self.loans = temp

def get_max_repay_income(self):
if self.loans:
return max(i[4] for i in self.loans)
else:
return 0

def pay(self):
self.loans = sorted(self.loans, key=lambda x: x[4])
payment = self.loans.pop()
print(f"还款前现金金额: {self.cash}\t\t\t偿还贷款: {payment[:-1]}\t\t剩余现金金额:{round(self.cash-payment[-2], 2)}")
if self.cash >= payment[-2]:
self.cash = round(self.cash - payment[-2], 2)
else:
min_aount = self.min_loan_amount(payment[-2], self.cash)
self.cash = round(self.cash + min_aount - payment[-2], 2)
self.loans.append([min_aount, 5, min_aount, min_aount, 0]) # 假设为还款而产生的借款都为5期
print(f"还款时暂时借款:{min_aount}")

@staticmethod
def repay_income_1(principal, balance) -> float:
"""
当手中现金大于待还款金额时,计算还款收益。
:param principal: 贷款剩余本金
:param balance: 剩余还款总额
:return:
"""
return principal*LOAN_RATIO - balance*CASH_RATIO

@staticmethod
def repay_income_2(principal, balance, min_loan) -> float:
"""
当手中现金小于待还款金额时,先借到使手中金额大于待还款金额的最小借款。在综合计算还款收益。
:param principal: 贷款剩余本金
:param balance: 剩余还款总额
:param min_loan: 最小借款金额
:return:
"""
return (principal-min_loan)*LOAN_RATIO - balance*CASH_RATIO

@staticmethod
def min_loan_amount(balance, cash_num):
"""
剩余还款金额大于手中现金是,计算满足规则且覆盖还款金额的最小借款金额。
规则是借款金额必须是100 的整数倍
:param balance: 待还款金额
:param cash_num: 手中现金
:return:
"""
from math import ceil
diff = balance - cash_num
return 100*ceil(diff/100)


class GenerateData(object):
"""
用于生成满足规则的测试数据。
"""

def __init__(self, max_cash, loans_num):
self.max_cash = max_cash
self.loans_num = loans_num

def gen(self):
cash = round(random.random()*self.max_cash) # 随机生成手上现金数。
loans = [self.get_loan() for _ in range(self.loans_num)] # 随机生成若干笔贷款

return cash, loans

@staticmethod
def get_loan():
loan = random.randint(5, 400) * 100 # 生成[500, 40000]的借款金额
months = [5, 10, 20][random.randint(0, 2)] # 随机生成借款月数
days = random.randint(0, 30*months) # 生成[0, 720]天的借款天数(已发生)
days_1 = random.randint(30, 60) # 借款时距下一固定还款日期天数

over_months = (days - days_1)//30 + 1 # 已还款周期数

remain_loan = round(loan*(1 - over_months/months), 2) # 剩余借款本金
remain_loan_inter = round(remain_loan *
(1 + LOAN_RATIO * (days - (30 * (over_months - 1) + days_1))), 2) # 截止现在剩余还款总额(含息)

return [loan, months, remain_loan, remain_loan_inter, 0]


if __name__ == '__main__':
cash_0, loans_0 = GenerateData(100000, 10).gen()

print(f"初始现金情况:{cash_0}")
print(f"贷款数据说明: [初始贷款金额, 贷款期数, 剩余贷款本金, 剩余贷款总额]")
print(f"初始贷款情况:{[i[:-1] for i in loans_0]}\n\n")

loans_, cash_ = Repay(loans=loans_0, cash=cash_0).plan()
print(f"\n\n剩余贷款:{[i[:-1] for i in loans_]}")
print(f"剩余现金金额:{cash_}")

示例结果

Markdown

本来,我以为这是一个不太需要关注的问题,之前也遇到过几次了,但是都是搜索引擎一搜然后解决了就没管了.

然后今天在写一个包的过程中又遇到了,又坑了我半天时间,而且搜索引擎找到的解释也大都模凌两可(包括Google),这让我深深的觉得自己离理解这个bug还有点距离,故要记录一下.感觉不搞清楚一点以后还会被坑.

状况

我觉这里的状况还有几个层级,但是导致的结果都是[你的包不能用啦!你走错路啦!]
(因为原来会把这个问题想得很简单,找到一个方案之后,感觉自己似乎已经解决了,但还是报错,就会一直盯着看直到怀疑人生)

目前主要有3点,及解决方式.

遇到的3种情况

  • main‘ is not a package
  • Unresolved reference
  • ModuleNotFoundError: No module named ‘dir1’

接下来我要比较详细的记录一下几个问题的过程及处理步骤,主要是深化自己的理解,也希望会对遇到同样问题的同学能有帮助.
当然我的理解可能也不是基础理解,可能会有错漏,所以也请谨慎参考,欢迎拍砖交流.

项目结构

1
2
3
4
5
6
7
testmodule
├── dir1
│   ├── dir1_1_f.py
│   └── dir1_2_f.py
├── dir2
│   └── dir2_f.py
└── test_main.py

整个目录放在testmodule的根目录下,有一个主测试文件test_main.py和两个子文件夹,内容分别如下:
testmodule/test_main.py

1
2
3
4
5
6
7
8
9
from dir1.dir1_1_f import dir1_1_fun
from dir2.dir2_f import dir2_fun

def test_main():
dir1_1_fun()
dir2_fun()

if __name__ == '__main__':
test_main()

testmodule/dir1/dir1_1_f.py

1
2
def dir1_1_fun():
print('dir1_1_fun')

testmodule/dir1/dir1_2_f.py

1
2
3
4
5
6
7
from dir1_1_f import dir1_1_fun

def dir1_2_fun():
print('dir1_2_fun')

if __name__ == '__main__':
dir1_1_fun()

testmodule/dir2/dir2_f.py

1
2
def dir2_fun():
print('dir2_fun')

Debug

Markdown
Markdown
可以看出其实不加__init__.py文件也是可以跑的,使用的是相对路径.
所以不打包时,有无__init__.py都不重要,因为相关的执行文件都在,是都可以执行的.

但是如果打包的时候没有__init__.py的话,那么相关的子模块是不打包的,所以肯定会报错.

main‘ is not a package

使用相对路径导入且通过 python xxx/xxx.py调用时会出现
例如:将test_main.py中

1
2
from dir1.dir1_1_f import dir1_1_fun
from dir2.dir2_f import dir2_fun

改成:
1
2
from .dir1.dir1_1_f import dir1_1_fun
from .dir2.dir2_f import dir2_fun

通过
1
python testmodule/test_main.py

调用便会报上面错误.
需要通过
1
python -m testmodule.test_main

调用

Unresolved reference

“项目结构”中的代码在pycharm中应该会提示如上信息,因为示例代码中的引用还是绝对引用,但是却没有根目录信息,虽然可以执行,但是会有如上提示.
一是改成相对路径,二是在前面再加上根目录名称,三是在Django中将根文件夹设置成source(但是这一步只是消除提示,并不解决代码结构问题)

ModuleNotFoundError: No module named ‘dir1’

这个就是在代码目录中使用了相对路径,但是在包调用的时候只将包名import了,使用的时候希望将其它函数索引出来便会遇到这个问题,因为是相对引用,所以仅导入包名时并不能索引到其下层函数.
这个代码结构不用改,需要在import的时候使用全路径import.
例如:
使用

1
from testmodule.test_main import test_main

而不是
1
2
import testmodule
testmodule.test_main.test_main # 这个便会报错: ModuleNotFoundError: No module named 'test_main'

总结

参考:使用相对路径导入包中子模块

在本地执行(不通过setup.py打包)的时候,有无__init__.py是没关系的,但是如果要打包且要使用子模块的话就必须含有__init__.py才能被视为子模块

建议都是用相对路径引用,在子模块之间使用“.”,“..”
使用相对路径后:
在本地测试的时候不能用python xxx/xxx.py来运行单元测试.需要改成python -m xxx.xxx
在打包后使用时,需要在import时使用全路径,不能是只导入顶层包名,然后通过包名索引

类似于人名/地名/组织机构名的命名体识别数据集,我花了几天时间标注了大约10000条视频/音乐/书籍数据.

数据的意义希冀能够基于此训练NLP模型识别句子中的视频/音乐/书籍等名称信息.

数据的标注过程:

  • 先纯手动提取标记了一部分(大约5000条),基于标注数据训练一个base模型,基于base模型重新审视校正标注数据.
  • 基于校正后的数据再训练一个模型,基于模型标注了另外约5000条数据.并对数据进行人工审核校验.
  • 最终数据集包含9632条数据.

理论上来说,任务也会是标准的NER任务.
难点:同一个名称可能是书籍也可能是视频(电视电影可能是由小说改编而来,有些场景关注书籍,另外一些可能关注视频),有些句子则只是提供了一长串并列的名称,可能没有更多的辅助信息;

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
放暑假了,最近剧荒,陈情令也才一个星期更新三次,根本不够看,问问大家有什么好看的电视剧或电影推荐吗?最好是那种搞笑,温暖的那种,日剧也可以,好像道骏枝佑的剧还不错!
label: 陈情令/video

最近有没有好看的电视剧推荐,国内国外的都可以,前两天再追少年派,但剧情走向越来越扯,非常想给编剧寄刀片,现在想看些正常三观的剧,大家有没有推荐哒?
label: 少年派/video

最近有些剧荒啊,有什么好看的电视剧或者电影可以推荐么?我看的也比较杂,权力的游戏,黑色止血钳,最近看的韩剧囚犯医生是大爱啊,类似这种类型的可以给我推荐一些么?
label: 权力的游戏/video 黑色止血钳/video 囚犯医生/video

我个人比较喜欢听古风歌曲,然后呢,我歌单里面可以给你推荐几首,归去来兮琵琶行清明上河图好可以去试着搜索一些古装剧的主题曲或者插曲
label: 归去来兮/music 琵琶行/music 清明上河图好/music

不知道你喜欢什么类型的小说,最近在看十宗罪,悬疑烧脑类的,讲述的是公安部门打击违法犯罪的故事,现在已经出到第六部了,估计够你看一个月了。大冰写的书也可以尝试看一下,文艺小清新类型的
label: 十宗罪/book

最终提供的数据集转换成了标准的BIO标注格式,欢迎尝试使用.

Copyrights & Cite

LG: ourantech@163.com
Blog: https://www.ourantech.club/2019/08/31/029_视频音乐书籍标注数据/
Github: https://github.com/LG-1/video_music_book_datasets