Deep Coding


  • 关于

  • 标签

  • 日志

  • 首页

python操作(二):字符串

发表于 2018-01-30 |

找出最后一个以’.’开始的字符串

1
2
3
4
5
6
7
# 第一种方法
filename = "/home/tucodec/ZJU.mp4"
str_1 = filename[filename.rindex("."):] # rindex:返回最后一个'.'的位置信息
print(str1) # 输出“.mp4”

# 第二种方法
str_1 = filename.split('.')[-1]

获取某文件夹下包含文件名称的绝对路径

1
2
3
4
5
6
7
def all_path(dirname):
result = []
for maindir, subdir, file_name_list in os.walk(dirname):
for filename in file_name_list:
apath = os.path.join(maindir, filename)
result.append(apath)
return result

获取文件下文件名称的另一种方式

1
2
3
4
5
6
7
result = [] 
alllist=os.listdir("/Users/wxj/Desktop/1")
for i in alllist:
if "." in i:
aa, bb=i.split(".")
if bb == "txt":
result.append(aa+"."+bb)

还是获取文件列表到一个list,并排序

glob模块提供了一个函数用于从目录通配符搜索中生成文件列表:

1
2
3
4
5
import glob
list_1 = glob.glob('*.jpg')
# ['2.jpg', '1.jpg', '5.jpg']
sorted(list_1)
# ['1.jpg', '2.jpg', '5.jpg']

3种连接字符串方式

1
2
3
4
5
6
7
8
9
10
11
seq1 = ['hello','good','boy','doiido']
print ' '.join(seq1) # 已空格符连接一个list中的字符串
输出:hello good boy doiido
===============================================
os.path.join('/hello/','good/boy/','doiido') # 合并目录,注意‘/’不能多写
输出:'/hello/good/boy/doiido' # 这样可以正确处理不同操作系统的路径分隔符
===============================================
a = '/hello/'
b = 'good/boy/'
print(a+b)
输出:'/hello/good/boy/'

分割字符串成list

1
2
3
line = "我们 大 中国"
line.split()
['我们', '大', '中国']

删除不同位置的空格

1
2
3
4
5
6
7
8
s.strip(rm)        #删除s字符串中开头、结尾处,位于rm删除序列的字符
s.lstrip(rm) #删除s字符串中开头处
s.rstrip(rm) #删除s字符串中结尾处
# rm删除序列是只要边(开头或结尾)上的字符在删除序列内,就删除掉
a = '123abc'
a.strip('21')
a.strip('12')
# 结果是一样的:'3abc'

python全局变量

Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块是不会引入新的作用域的。

1
2
3
4
5
6
7
8
9
10
11
12
if True:
msg = 'I am from Runoob'
print(msg) # 'I am from Runoob'
#=============global=======nonlocal===================
global str1 # 在给全局变量str1赋值时,需要先声明global,否则str1会被当作新定义的局部变量
str1 = "new year"
str2 = str1 # 全局变量赋值给另一个局部变量时,不需global声明,程序会先找局部变量str1,没得话就去全局变量区域找
def func():
str1 = "中国"
def func_1():
nonlocal str1
str1 = "南京" #修改嵌套作用域(函数A的变量作用在子函数B时)中的变量需要使用 nonlocal 关键字

python list、dict、tuple、set

set:通过set.add()函数添加元素,set会自动去重,且查找效率高(hash)
tuple是只读的list(),list可以通过下标、循环等方式访问,通过.append()来访问
dict是key、value一一对应的关系,常用于序号跟真值之间的查询,比如NMT任务的vocab

1
2
3
4
dict1 = {'1':'飞机','2':'自行车','3':'鸟','4':'船','5':'瓶子','6':'公共汽车','7':'车','8':'猫','9':'椅子','10':'牛','11':'餐桌','12':'狗', \
'13':'马','14':'摩托车','15':'人','16':'植物','17':'羊','18':'沙发','19':'火车','20':'电视'}
for str_i in list(A):
List.append(dict1[str_i]) #listA中是一个纯数字列表,LIST中对应其在字典中的真实类别

write、writelines区别

1
2
f.write(name + '\n') # write只能写入str
f.writelines(text) # 自动迭代写入一个字符串列表[str]

文件批量命名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import os
f = sorted(os.listdir())
n = 0
for i in f:
if n < 9:
oldname = f[n]
newname = "000"+str(n+1)+".jpg"
os.rename(oldname, newname)
print(oldname, "-------->", newname)
elif n < 99:
oldname = f[n]
newname = "00"+str(n+1)+".jpg"
os.rename(oldname, newname)
print(oldname, "-------->", newname)
elif n < 999:
oldname = f[n]
newname = "0"+str(n+1)+".jpg"
os.rename(oldname, newname)
print(oldname, "-------->", newname)
n += 1

python操作(一):网络编程入门

发表于 2018-01-30 |

背景简介

最近做的文本相似度匹配任务,需要部署到web端,毫无网络背景知识的笔者在老板的“压榨”下,3天速成了python websocket通信,并于公司json小哥合作,实现了图片、文本的server-client端通信。

整个要完成的架构如上图所示:Client send一个“字符串/图片”给Server,Server进行一系列运算后,send“字符串/图片”给Client,完成一次通信。在这里我们只记录红块、黄块内的工作内容。

网络进程之间如何通信?

TCP/IP协议可以唯一的标识网络中的主机,即“网络层的IP地址”;主机中的应用程序(进程),通过“协议+端口”的形式指定;利用这样的“IP地址+协议+端口”的三元组,就可以标识网络中的唯一一个进程了
而编写/操作TCP/IP协议的应用程序接口,通常采用socket套接字。有一些前辈的直观解释是:socket类似于文件操作的“open—write/read—close”模式,只是将其应用在了TCP/IP协议上

socket的基本操作函数:

socket()函数

int socket(int domain, int type, int protocol)
socket函数对应于一个普通的文件打开操作,返回一个socket描述字,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。通过调用bind()函数给该描述字分配一个端口(PORT),如果不指定会随机分配
domain:地址类型,如:AF_INET决定了要用ipv4地址
type:socket类型
protocol:TCP/IP等传输协议,默认选择跟type类型对应的协议

bind()函数

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器,所以通常服务器端在listen之前会调用bind()

listen()函数

int listen(int sockfd, int backlog);
作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。
第一个参数是socket描述字,第二个参数是socket可以排队的最大连接个数

accept()函数

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
Server端通过socket(),bind(),listen()操作,就可以监听指定的socket地址了。Client端通过requests.get(url)函数建立连接请求,Server端监听到后调用accept()接收这个请求,之后就可以进行两端的I/O操作了。

close()函数

传输完成需要关闭连接。以上是socket的C代码解读,python中的非常类似,如下是用python写的socket传输字符串,基本都可以看懂。
Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import socket
HOST, PORT = '', 8888 # Server端网络IP及端口
listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listen_socket.bind((HOST, PORT))
listen_socket.listen(1) #设置监听,最多有5个端排队等侯
print 'Serving HTTP on port %s ...' % PORT # 开启了一个侦听socket
while True:
coon, addr = listen_socket.accept() # 开启一个永续循环,被动等待客户端连接
request = coon.recv(1024) # 接收bytes类型数据,每次接收1024b
accept_data = str(request, encoding="utf8")
#print(request)
print("".join(("接收内容:", accept_data, " 客户端口:", str(addr[1]))))

http_response = "Hello World!"

conn.sendall(bytes(http_response, encoding="utf8")) # 重新建立连接后,返回一个服务器中预定义的字符串
coon.close()

Client

1
2
3
4
5
6
7
8
import socket
sk = socket.socket()
sk.connect(("192.168.1.136", 8888)) # 与局域网内Server端IP地址Connect
send_data = "Hello Server"
sk.sendall(bytes(send_data, encoding="utf8"))
accept_data = sk.recv(1024)
print(str(accept_data, encoding="utf8")) # 接收到Server端发来的“Hello World!”
sk.close()

Tips:

  • 需要注意send、recv函数必须是一一对应的,有接必有收,逻辑上必须足够清楚。否则会造成不知名的一些Error。
  • 在python3中,所有数据的传输必须用bytes类型(bytes只支持ascii码)所以在发送数据的时候要么在发送的字符串前面加 ‘b’,要么使用encode(‘utf-8’)进行转换成bytes类型发送

通过Socket在局域网内传输图片/视频/PDF等

通过上面的工作,已经能够实现普通字符串的传输了,那么怎样搭建一个通用的文件传输系统呢?这里只需要加一个Trick,依然是每次recv(1024),但要循环接收,直到文件全部被传输完毕为止。关键代码如下:
Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import os,socket,hashlib
# ...
# socket监听定义
# ...
while True:
conn, addr = listen_socket.accept()
request = conn.recv(1024)
data = str(request, encoding="utf8")
print("接收内容:", data)
filename = "/home/tucodec/ZJU.mp4"
if os.path.isfile(filename):
f = open(filename,"rb")
m = hashlib.md5() # 哈希码用来加密文件
file_size = os.stat(filename).st_size
conn.send(str(file_size).encode("utf-8")) # send size to client,计算需要循环多少个1024 byte
conn.recv(1024) # 联系两个send会发生粘包,加一个recv
for line in f:
m.update(line)
conn.send(line) # 循环发送
f.close()
print("send done...")

Client

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
import socket,hashlib
while True:
sk = socket.socket()
sk.connect(("192.168.1.136", 8888)) # 主动初始化与服务器端的连接
while True:
strs = input("请输入:")
if len(strs) == 0:
continue
sk.send(bytes(strs, encoding="utf8"))
server_resp_size = sk.recv(1024) # 接收send_filesize,bytes类型的数据
file_total_size = int(server_resp_size.decode()) # 格式转换
sk.send(bytes("开始接收文件...", encoding="utf8"))
f = open('new.mp4', "wb")
m = hashlib.md5()
recv_size = 0
while recv_size < file_total_size:
if file_total_size - recv_size > 1024: #最后一次接收之前,接收大小设置为1024
size = 1024
else: #最后一次不足1024,则只接收文件剩余的部分
size = file_total_size - recv_size
data = sk.recv(size)
recv_size += len(data)
f.write(data)
m.update(data)
f.close()
sk.close()
break

aaugustin/websockets

尝试了socket的细节后,对网络编程有了些基础的认识。但是要和json小哥通信还是不行的,用上述的socket.recv(1024)方法得到的,是小哥发过来的是Http报头,需要各种校验,才能完成握手。果断上Github寻找别人写好的包,这时aaugustin/websockets以其简洁管事儿进入了我的视线,直接把文档中的例子拿来跑便可以一目了然。
Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python

import asyncio
import websockets

async def hello(websocket, path):
name = await websocket.recv()
print("< {}".format(name))

greeting = "Hello {}!".format(name)
await websocket.send(greeting)
print("> {}".format(greeting))

start_server = websockets.serve(hello, '', 8888)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python

import asyncio
import websockets

async def hello():
async with websockets.connect('ws://192.168.1.136:8888') as websocket:
name = input("What's your name? ")
await websocket.send(name)
print("> {}".format(name))

greeting = await websocket.recv()
print("< {}".format(greeting))

asyncio.get_event_loop().run_until_complete(hello())

查看某个网络端口占用并将其Kill

1
2
$ lsof -i :8888
$ kill 4688

Refer:

aaugustin/websockets
Python HTTP 库:requests 快速入门
使用Python来编写HTTP服务器的超级指南
网络编程学习笔记一:Socket编程
Python3 Socket网络编程
Python 3.x–Socket实现简单的ssh和文件下载功能

文本语义匹配/相似度度量

发表于 2018-01-30 |

Word2vec语义相似度计算

上一章节,我们用1.1G的中文维基百科语料训练了词向量。而短文本的语义相似度度量,最简单的方法便是把每个句子的词向量相加,然后计算其余弦相似度。gensim包中同样用函数实现了这一算法,如下:

1
2
3
4
def n_similarity(self, ws1, ws2):
v1 = [self[word] for word in ws1]
v2 = [self[word] for word in ws2]
return dot(matutils.unitvec(array(v1).mean(axis=0)), matutils.unitvec(array(v2).mean(axis=0)))

建立一个target.txt文件存放待匹配的问题,代码比较简单如下:

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
# -*- coding:utf-8 -*-
#!/usr/bin/env Python
from gensim.models import Word2Vec
import sys
import jieba
jieba.load_userdict("dict.txt")
class ResultInfo(object):
def __init__(self, index, score, text):
self.id = index
self.score = score
self.text = text

target = "target.txt" # 问题集合
model = "./model/word2vec"

model_w2v = Word2Vec.load(model) #加载模型 文字摘要
print ("模型加载成功!")
candidates = []
with open(target) as f:
for line in f:
candidates.append(line.strip().split()) # candidates列表中加载所有问题,
并用空格分割
while True:
text = input("请输入查询指令: ")
words = list(jieba.cut(text.strip()))
if len(words) == 0:
continue
flag = False
for w in words:
if w not in model_w2v.wv.vocab:
print ("输入指令中词汇“%s”不在字典中" % w)
flag = True
if flag:
continue
res = []
index = 0
for candidate in candidates:
score = model_w2v.n_similarity(words, candidate)
res.append(ResultInfo(index, score, " ".join(candidate)))
index += 1
res.sort(key=lambda x:x.score, reverse=True)
for i in res:
print i.text # 输出相似度最高的语句

最后测试结果如下,在短文本的相似度测量上,基于词向量相似度度量的算法在计算速度和质量上均是比较满意的。

============体育=============
3 世界 锦标赛 中国队 胜出 0.42
4 中国 足球 失败 0.41
2 奥运会 中国 女排 夺冠 0.40
============农业=============
1 农民 在 江苏 种 水稻 0.55
0 人工智能 和 农业 怎么 结合 0.45
5 山东 苹果 丰收 0.42

Doc2vec语义相似度计算

Doc2vec的原理暂未深入研究,参照网上各路教程跑通后的代码如下。笔者用该doc2vec进行了语义相似度检测,但效果差强人意(具体来说,能捕捉到一定程度句子结构信息,但对语义很模糊)。可能跟语料库选取关系较大,此处用的不是一行行的短句,而是训练word2vec的语料,一行一段话…接下来若有文本分类等任务,再补上详细研究路线

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
# -*- coding:utf-8 -*-

import gensim
import smart_open
import multiprocessing
import logging
from gensim.models.doc2vec import TaggedDocument, Doc2Vec

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

data_set = "pre0"

def read_corpus(fname, tokens_only=False):
with smart_open.smart_open(fname, encoding="utf-8") as f:
for i, line in enumerate(f):
if tokens_only:
yield gensim.utils.simple_preprocess(line)
else:
text = line.split()
yield TaggedDocument(text[:], [i])


train_data = list(read_corpus(data_set))
print len(train_data)

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
model = Doc2Vec(size=100, min_count=1, iter=10, workers=multiprocessing.cpu_count())
model.build_vocab(train_data)
model.train(train_data, total_examples=model.corpus_count, epochs=100)
model.save('./model/model_new.doc2vec')

Ref:

LSTM 句子相似度分析
基于gensim的Doc2Vec简析

中文维基百科词向量Word2vec实战!

发表于 2018-01-25 |

上一篇文章详细学习了Word2vec的含义及实现原理,包含两大语言模型:Cbow、Skip-Gram,以及两大快速训练技巧:Negative sampling、哈夫曼树形式查找Target word。这一篇主要讲基于中文维基百科语料库的Word2vec训练,采用了开源的python gensim包

安装gensim包

Google最初开源的Word2vec是用C++写的,后来陆续出现了很多版本,我们采用的是gensim ,gensim已经被集成在了conda中,所以我们可以方便地利用miniconda进行安装。

1
2
source activate py2k  # 激活python2环境,当然python3也可以的,只不过笔者此处用的python2版本
conda install gensim

获取维基百科语料库

下载

2017版百科语料下载地址为:https://dumps.wikimedia.org/zhwiki/latest/zhwiki-latest-pages-articles.xml.bz2。 下载后是一个压缩包,里面的内容是从网上爬取的“标题–摘要–正文”对,以及其他的html、json标志符等。

语料抽取

Wikipedia Extractor是一个开源的语料抽取工具,语料库压缩包不用在本地解压,直接用Wikipedia Extractor脚本执行:

1
2
3
4
$ git clone https://github.com/attardi/wikiextractor.git wikiextractor
$ cd wikiextractor
$ sudo python setup.py install
$ ./WikiExtractor.py -b 1024M -o extracted zhwiki-latest-pages-articles.xml.bz2 #

其中2000M代表抽取后的预料库最大允许size,因为原始语料库size约1.3G,这里用2000M的话相当于把语料全部抽取到一个文本文件“wiki_00”中。extracted代表抽取后语料存放路径。
最终控制台log输出:INFO: Finished 7-process extraction of 986891 articles in 1393.9s (708.0 art/s),代表全部抽取完成共986891篇文章。
抽取后的内容格式为:每篇文章被一对<doc>、</doc>包起来,而<doc>中的包含的属性有文章id、url和title属性,如<doc id="xxx" url="https://zh.wikipedia.org/xxx" title="xxx">。

繁体字转简体

上一步抽取的中文维基百科语料库比较杂乱,既有繁体字也有简体字,这里需要将其统一变为简体字,采用开源的 OpenCC转换器。ubuntu下使用方法如下:

1
2
$ sudo apt-get install opencc
$ opencc -i wiki_00 -o zh_wiki_00 -c zht2zhs.ini

去除标点

去除标点是因为:训练标点符号意义不大,且标点符号出现次数过多,影响一定训练效率。可以通过正则表达式去除。感谢吴良超的学习笔记提供的去标点程序,使用格式为:

1
python script.py input_file output_file

pre1.py源码如下:

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
#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import re
import io

reload(sys)
sys.setdefaultencoding('utf-8')

def pre_process(input_file, output_file):
multi_version = re.compile(ur'-\{.*?(zh-hans|zh-cn):([^;]*?)(;.*?)?\}-')
punctuation = re.compile(u"[-~!@#$%^&*()_+`=\[\]\\\{\}\"|;':,./<>?·!@#¥%……&*()——+【】、;‘:“”,。、《》?「『」』]")
with io.open(output_file, mode = 'w', encoding = 'utf-8') as outfile:
with io.open(input_file, mode = 'r', encoding ='utf-8') as infile:
for line in infile:
line = multi_version.sub(ur'\2', line)
line = punctuation.sub('', line.decode('utf8'))
outfile.write(line)

if __name__ == '__main__':
if len(sys.argv) != 3:
print "Usage: python pre1.py input_file output_file"
sys.exit()
input_file, output_file = sys.argv[1], sys.argv[2]
pre_process(input_file, output_file)

去除标识符及分词

经过上一步处理,每一篇文章被存放在了doc/doc标签。同时肉眼对比抽取后的句子,每一段落的开头跟标题都是重复的,也需要去除。还有前辈们建议把一篇段落的文章放到一行,但词向量关注的window size`一般大小为3、5,感觉也没这个必要。安装jieba分词,pre2.py代码如下:

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
#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import io
import jieba

reload(sys)
sys.setdefaultencoding('utf-8')

jieba.load_userdict("dict.txt") # 如果有的话加载用户自定义词典


def cut_words(input_file, output_file):
count = 0
a = 1 # 标识符
with io.open(output_file, mode = 'w', encoding = 'utf-8') as outfile:
with io.open(input_file, mode = 'r', encoding = 'utf-8') as infile:
for line in infile:
if a == -1:
a = a*(-1)
continue
line = line.strip()
if len(line) < 1:
continue
# 移除字符串首尾的\r \n 空格等字符,此处希望一篇文章的所有内容位于一行,使得训练预料更规整
if line.startswith('doc'):
if line == 'doc':
outfile.write(u'\n')
count += 1
str = "已完成 %d 篇百科文章转换" % (count)
print str
else:
a = -1
continue
else:
for word in jieba.cut(line):
outfile.write(word + ' ')

if __name__ == '__main__':
if len(sys.argv) < 3:
print "Terminal Format: python script.py input_file output_file"
sys.exit()
input_file, output_file = sys.argv[1], sys.argv[2]
cut_words(input_file, output_file)

最终log输出:已完成 986891 篇百科文章转换,代表全部文章转换完毕。

文本去重

NLP常规操作,这么大的数据量,采用利用哈希表检测重复并去除,pre3.py代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/python
# -*- coding: utf-8 -*-
#程序功能是为了完成判断文件中是否有重复句子
#并将重复句子打印出来

import shutil
res_list = set()
f = open('pre2','r')
w = open('pre3', 'w')
k = 1
index = 0
for line in f.readlines():
index = index + 1
if line in res_list:
k = k + 1
print("Repeat", k, "All:", index, "Percent: %.5f%%" % (100*k/index))
else:
res_list.add(line)
w.write(line)

最终log输出:Repeat', 18649, 'All:', 986884, 'Percent: 1.00000%',重复了1%的语料。

用gensim训练词向量

初始训练

解释下参数含义:
multiprocessing:python中的多线程模块,这里这里采用的是8核CPU进行训练,网络比较简单的情况下速度要比GPU快。
LineSentence:将输入文件转为 gensim 内部的 LineSentence 对象,要求输入的文件的格式为每行一篇文章,对于中文每篇文章经过分词处理。
min_count:低于min_count的词频的词不参与训练;sg:0表示CBOW语言模型,1表示Skip-gram模型;size:词向量维度
iter:迭代的epoch次数,这里着重讲下#此处的iter值会被直接设置到百分比进度条中,而不是日常训练神经网络一样epoch1、epoch2…epoch100

train.py代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/python
# -*- coding: utf-8 -*-

import os, sys
import multiprocessing
import gensim
import logging

reload(sys)
sys.setdefaultencoding('utf-8')

def word2vec_train(input_file, output_file):
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
sentences = gensim.models.word2vec.LineSentence(input_file)
model = gensim.models.Word2Vec(sentences, size=800, window=5, min_count=3, iter=100,sg=0, workers=multiprocessing.cpu_count())
# model.train(sentences, total_examples=model.corpus_count, epochs = 100) 默认参数,也可以指定
model.save(output_file)

if __name__ == '__main__':
if len(sys.argv) < 3:
print "Terminal Format: python script.py infile outfile"
sys.exit()
input_file, output_file = sys.argv[1], sys.argv[2]
word2vec_train(input_file, output_file)

最终log输出:2018-01-22 19:37:52,179 : INFO : PROGRESS: at 100.00% examples, 122591 words/s, in_qsize 16, out_qsize 0
代表完成训练。

  • 实验环境:ubuntu16.04、 CPU:8核
  1. 迭代5次epoch,耗时1.5小时
  2. 相同参数下,迭代100次epcoh,耗时40小时

增量训练

增量训练可以在新的语料库上,基于原有模型进行叠加训练,而不用从头开始。
train_add.py关键部分代码如下:

1
2
3
4
5
6
def word2vec_train(input_file, output_file):
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
model = gensim.models.Word2Vec.load('word2vec')
more_sentences = gensim.models.word2vec.LineSentence(input_file)
model.build_vocab(more_sentences, update=True)
model.train(more_sentences, total_examples=model.corpus_count, epochs=model.iter)

上面的代码先加载了一个已经训练好的词向量模型,然后再添加新的文章进行训练,同样新增的文章的格式也要满足每行一篇文章,每篇文章的词语通过空格分开的格式。由于前段时间参加Ai Challenge比赛,正好有1000W行处理后的中文数据可以加入训练。

使用词向量模型

直接上代码了,infer.py如下:

1
2
3
4
5
6
7
8
9
10
#encoding=utf-8
import gensim
from gensim.models import word2vec
model=gensim.models.Word2Vec.load('word2vec')
print "模型加载成功!"
print "----词向量维度------"
print len(model[u'男人'])
print "----跟“滋润”相近的词------"
for i in model.most_similar(u"滋润"):
print i[0],i[1]

Refer:
word2vec实战:获取和预处理中文维基百科(Wikipedia)语料库,并训练成word2vec模型
中文维基百科语料库词向量的训练
不可思议的Word2Vec

Word2vec词向量原理解析!

发表于 2018-01-21 |

1. 词汇表示Background

one-hot编码

文本、字符串看似简单,其实是经过几千万年的演化,人类抽象出的非常高维、稀疏的特征。拿汉语来说,词汇数量约为几十万。如果采用ont-hot形式编码,一个词语的维度就将占据几十万维。训练时候往往是上亿个词,这便会造成巨大的维数灾难。

Word2vec

而Word2vec(中文名:词向量/词嵌入),可以自由控制词汇的维度,常用的维度数一般为256、512…1024维。相对于one-hot编码词汇的离散表示,Word2vec是一种词汇的分布式表示(distribution representation)。比如某个词汇被以维度为[1,256]的一维向量表示,如果是一个包含100个词的句子,那么这句话被表示为[100,256]。写到这里,其实便可以意识到这跟图像用像素矩阵来表示是非常类似的。
图片名称

Word2vec特点

因为Deep learning的优势是从低维特征中自动学习、提取高维特征,词的分布式表示便可以充当词汇的低维特征表示,所以目前像文本分类、机器翻译、摘要等NLP任务,都使用Word2vec表示的词汇来作为模型的input。使得NLP领域也因为Deep learning 取得了不错的进步。如下是搜索“爱因斯坦”的相近词汇

----跟“爱因斯坦”相近的词------
宇宙学 0.808888375759
牛顿 0.800362348557
相对论 0.791419327259
定律 0.76971578598
拓扑 0.764759659767

但因为Word2vec衡量的是文本共现程度,只能反映两者经常在相近的上下文环境中出现。所以它并不是真正意义上的语义表示,只能说一种近似,或者说其中存在很多的语义误差,并不像像素可以精确表示一副图像的底层信息。笔者认为这也是造成NLP领域并没有像图像、语音识别领域取得非常长足进步的重要原因。

由于词汇背后丰富的语义信息,并不能通过Word2vec完全表示,最近也有学者尝试用更细的粒度来表示词汇。清华大学的刘知远老师利用HowNet:一种用最基本的、不宜再被分割的最小语义单位来表示的语义体系,跟Word2vec相结合,也获得了一些可以展望的成果《在深度学习时代用HowNet搞事情》


Word2vec重要组件

Word2vec(以下简称词向量),虽然作为概率语言模型的input,但其实是训练语言模型产生的中间结果。

  • 两个语言模型:
    • Skip-gram:根据当前词语wt,预测上下文的词语(wt-1、wt+1)的模型。
    • CBOW:Continuous Bag-of-Words Model的缩写,与Skip-gram正好相反
  • 优化Softmax归一化:
    • Hierarchical Softmax:采用哈夫曼树思想分层求取Target概率
    • Negative sampling:softmax时仅采样少量词汇作映射

2. Word2vec重要组件详解

神经网络语言模型简化

简化后训练Language model的神经网络如图所示,甚至都没有Hidden Layer层,是一个直接从input到output的映射。
图片名称
图中的三层含义:

input:上下文的词向量(注意我们训练的是CBOW语言模型,词向量只是模型的一个参数,起始时是个随机值)
projection:上下文词向量simply add(若是Skip-gram为input本身)
output:按理说此处应当是在词表维度C上的概率分布

参数化表示上面的CBOW语言模型后,如下图所示:(看这里更清楚了,这不就是纯粹的softmax公式么…)
图片名称
简单计算下这个网络从projection Layer到Output Layer 全连接网络层之间的参数:projection Layer即词向量维度在102量级;词表(Vocab)数量在105量级;语料库中需要训练的词汇数量一般在107量级以上。这样的运算次数是十分吓人的!

Hierarchical Softmax

重点:作者此处便采用了哈夫曼树的思想:具体做法是将语料库中的词按先词频排序,然后不断合并最小的两个节点成为一棵哈夫曼树,最终合并效果如下所示,词表中的所有词汇均为于树的叶节点上(详见:10分钟便能懂的公开课)
图片名称
生成Target词汇的概率为从跟节点到叶子节点路径的概率连乘(见下图)。而哈夫曼树相比于其他形式的二叉树,将高频词赋予更短的编码,可以进一步减少概率P相乘次数。下面用图解的方式说明为什么采用哈夫曼树可以加快计算速度。

极限假设词向量维度为3,词表大小为20。那么全连接网络1次迭代需要训练的参数数量为60个,接着再进行softmax归一化。
哈夫曼树的形式,假设目标词汇是图中的节点8,根结点到8节点一共经过5个节点(哈夫曼树编码知识,8节点的编码表示为:11110,那么计算Target词汇概率,仅涉及3*5=15个参数的计算。

图片名称
基于二叉树的这种性质,计算复杂度从之前的O(N)变为O(logN),运算速度指数级提升。
而Cost函数为对概率连乘取log对数,如下:
图片名称
图片名称

训练目标是最大化Cost函数,采用的算法是梯度上升。其实跟梯度下降非常类似,cost对参数向量取偏导数得到梯度,参数更新时候再加上该梯度乘learning rate就可以了,不过此处涉及两个参数向量,一个是权重theta,另一个就是词向量。此处直接复制Hankcs.com中的推导结果,两个参数更新方式如下:
图片名称
图片名称
如此便完成了一次完整的训练周期!
更简单直白的总结是:扁平的softmax在计算P(y=j),需要计算整个词表每个词的概率,而现在分层的softmax只需要计算一条路径上节点的概率值,便可以得到P(y=j),接着进行梯度上升,不断使概率P变大


Negative sampling

定义:

由于Hierarchical Softmax对于频率较低的词汇,依然需要花费较长的步骤才能找到该词。所以作者引入了另一种算法:Negative sampling(sampled softmax),即:为了加快训练速度,在loss计算时,采用负采样方式,并不是求整个vocab的softmax概率,而是随机采样的W个。这样每次训练中就可以更新这少量的W个参数,其他参数被冻结(需要注意总的参数数量不变,只是每次参与迭代计算的数量锐减)。
需要注意:在Test阶段,要到整个词表中softmax(此时词向量本身及其权重已经被计算了出来),来确定下一次选中的是哪些词汇(可接beam search)。若是哈夫曼树,Test时会找出一个确定的词
我们原来模型每次运行,假设词向量维度:300;vocab大小:1W,之前每次迭代需要跟新300W参数。现在负采样个数N=5。只要跟新300×(1+5)=1800,参数减少了非常多,训练速度大幅加快。

采样方法:

那怎样确定负采样个数N呢?其实就是随机抽取,但要考虑词频的影响,词频越高,越容易被抽出来。具体到word2vec中,作者是这样实现的:采用一个大小为M的table(M>>vocab size,一般取10^8)。分为M份,每份代表一个词,如图所示;
图片名称
但具体怎么确定哪一份对应什么词呢?作者采用了如下的公示计算每个词的份额(对应在tabel上占据的长度)
图片名称
在paper中提到0.75这个超参是试出来的,实现的话就是直接1~M取一个随机数,从中提取对应的项便可以了。


Ref:

Blog:Word2vec原理推导与代码分析
Blog:Word2vec-知其然知其所以然
Blog:中文维基百科语料库词向量的训练
Blog:Word2vec教程-Negative Sampling
《word2vec中的数学原理详解》
《Tensorflow实战》

tmux——轻松实现进程后台托管!

发表于 2018-01-17 |

Background

远程连接服务器跑模型时,经常由于客户端IP地址发生变化,或者关闭Terminal窗口,而导致服务器Session终端,这里介绍一种可随时从Session中抽离,又能在需要时候重新回到该Session的方法

以下程序均为在服务器端操作,或者SSH远程服务器操作

1、服务器端安装tmux
$ apt-get install tmux
2、Terminal窗口直接键入tmux,进入一个session,左下角显示该session进程号
$ tmux

现在便可随时关闭Terminal,Session便可以在后台安静稳定地运行!需要恢复该Session时:

$ tmux attach -t 0
3、杀掉该session

在某个tmux窗口内,输入exit便可以kill并退出

$ exit

virtualenv切换python2/3环境!🐶

发表于 2018-01-16 |

Background

Python的全局锁有时候非常烦。很多人电脑里都会同时装python2、python3。但在linux安装各种包时,长期混乱地pip、pip3随便安装,时间久了便造成安装环境混乱,导致“明明安装了某种包,在使用时却未发现该包的错误” ,而virtualenv就是用来为一个应用创建“隔离”的Python运行环境

virtualenv安装
1
pip3 install virtualenv
进入到独立的某个项目目录中
1
2
3
cd Test
virtualenv venv --python=python3 # 将系统的python3环境单独复制一份过来
source venv/bin/activate # 激活该venv环境,修改相关环境变量,让命令python和pip均指向当前的virtualenv环境

注意到命令提示符变了,有个(venv)前缀,表示当前环境是一个名为venv的Python环境

正常安装其他包便可以了
1
2
pip install numpy
deactivate # 退出前面建立的venv到系统目录下

知识图谱私见!🐶

发表于 2018-01-14 |

背景:

2012年5月,Google推出Google知识图谱(GoogleKnowledge Graph),并利用其在搜索引擎中增强搜索结果。这是“知识图谱”名称的由来,也标志着大规模知识图谱在互联网语义搜索中的成功应用。

Eg:搜索“吴彦祖的国籍是什么?”,Google/Baidu会给出与之相关的详细搜索结果(可以叫“搜索++”或者“搜索杂烩且精准”,顺带提一句,搜索本身就是AI,没必要将其神秘化,之前到现在一只都有,只是如同其他任何一个行业,发展的越来越好,2012年后随着深度学习的爆发,迎来了一波小高潮)

知识图谱概念

定义:知识图谱旨在描述真实世界中存在的各种实体或概念及其关系,其构成一张巨大的语义网络图,节点表示实体或概念,边则由属性或关系构成。

节点:知识图谱包括实体、属性/语义类、属性值、关系这么4种节点。最终以三元组的形式呈现,主要包括(实体1-关系-实体2)和(实体-属性-属性值)等。

中国-首都-北京是一个(实体-关系-实体)的三元组样例.
北京-人口-2069.3万是一个(实体-属性-属性值)的三元组样例。

构建:

知识图谱的构建技术包括3大方面:知识提取、知识表示、知识融合。后两方面自己目前的知识面尚未覆盖,仅就Deep Learning相关的来说,
知识提取部分又包括实体抽取、语义类抽取、属性和属性值抽取。

实体抽取目前有用BiLstm+CRF算法来做;
语义类抽取笔者自己也尝试了用word2vec进行相似度匹配,效果还不错;
属性和属性值抽取,笔者尝试了用命名实体识别工具+正则匹配的方式也能完成一定程度的,像地名、场景、日期、车牌等信息的抽取。

目前工业界基本也是通过从半结构化的数据中抽取信息,而不是通过“阅读”句子产生真正的理解来抽取,深层自然语言处理这块儿骨头太硬了。

小结:

写到这里,笔者更认为深度学习也是构建知识图谱的一种方式,深度学习能较好解决机器翻译、客服对话等小任务,也是一定程度上对知识地综合理解,比如通过机器翻译,建立英语、中文之间的联系,通过Imagenet图像分类,建立1000种Object的对应关系。但,深度学习在NLP/图像中对“语义”的理解,还是太过单薄,设计太过简陋,面对这些非结构化的大量知识体系,无从下口、束手无策。

能够将知识图谱的各条“边”剥离清楚的,深度学习还需要再向前推进一大步,远不止目前的CNN、RNN…
而知识图谱本身的发展,应该要求更高,需要的是更加综合的知识。

——不能真正创造价值的概念,都是耍流氓!

话说回来吧,借助某位哲人说的,希望自己摒弃热潮,以工程任务为导向。首要解决当下NLP能解决的任务,这也是本博客名称“Deep coding”的由来。


Hexo搭建Step by step! 👏👏👏

发表于 2018-01-06 |

A、Quick Start

Node.js官网下载对应的安装包:

[Download] (https://nodejs.org/en/)

安装Git:Mac用Brew安装,其他方法网上一大堆不再赘述
1
$ brew install git
Node和Git都安装好后,可执行如下命令安装hexo:
1
$ sudo npm install -g hexo
Desktop(或其它文件夹)中新建一个Blog文件夹,cd到新创建的Blog:
1
$ hexo init
至此:全部安装工作已经完成!生成静态页面:
1
$ hexo g
启动本地服务:
1
$ hexo server
此时浏览器输入 http://localhost:4000 即可访问原生默认网页

B、主题下载

此时如果想尝鲜,可以下载个新的主题到themes文件夹下,笔者用的主题是Download

修改根目录中_config.yml文件的theme项为新下载的主题的名字:

1
theme: hexo-theme-next-master

保存文件后,执行下面的指令,重新打开浏览器即可看到新的一片天地

1
$ hexo g    hexo server

C、绑定Github,实现远程访问

Github上建立一个与用户名【相同】的Repository
在建立的Repository Setting中,最后一项Deploy添加SSH key

获取SSH key的方式如下:

1
$ ssh-keygen -t rsa -C “wuxj231@163.com” # 这里填写你的Github地址

一路回车Ctrl,生成两个文件,用记事本打开其中的id_rsa.pub(执行上面Shell语句时会提示路径所在),复制其内容,粘贴到上一步Github/Repository Setting页面中的key目录中,Title随便取即可

编辑 _config.yml,翻到最下面,修改相应内容如下:
1
2
3
4
5
6
7
8
9
10
11
deploy:
type: git
repository: git@github.com:Wxjwjj/Wxjwjj.github.io.git
branch: master
```

##### 保存后,执行如下命令使用git部署,便会同步到Github目录

``` bash
$ npm install hexo-deployer-git --save
$ hexo deploy

此时可以通过Github链接 https://wxjwjj.github.io ,实现远程博客访问(如果本地文件更改后,均需要通过此步骤提交到Github,才能使变化生效)

Refer:

Welcome to Hexo! Check documentation for more info. If you get any problems when using Hexo, you can find the answer in [troubleshooting](https://hexo.io/docs/


1…45
祥吉

祥吉

CV/NLP,Studying,Coding

49 日志
10 标签
E-Mail 知乎 微博
© 2018 祥吉
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4
博客全站共37.2k字
本站访客数: