說(shuō)明
向量化將會(huì)是下一階段演進(jìn)的目標(biāo)。
在過(guò)去的實(shí)踐中,向量或者矩陣其實(shí)是最貼近工具端的。
以sklearn為例,雖然原始數(shù)據(jù)可能還是自然語(yǔ)言,但是在最終執(zhí)行 fit或者predict之前,數(shù)據(jù)一般都轉(zhuǎn)為了矩陣形態(tài)(numpy)。也就是說(shuō),在pandas(原始數(shù)據(jù))和最終結(jié)果(predict result)之間,是(短暫且必然)存在過(guò)矩陣的。
后來(lái),應(yīng)該是有過(guò)類似以圖搜圖類的應(yīng)用,向量化且持久化在數(shù)據(jù)庫(kù)中開(kāi)始興起。
之后,大規(guī)模向量化且持久化是從大語(yǔ)言模型爆火開(kāi)始的:因?yàn)榇罅康闹虚g結(jié)果需要重算(為向量),那么不如將中間結(jié)果固定住,需要的時(shí)候調(diào)用存儲(chǔ)就可以了。
所以一個(gè)明顯的趨勢(shì)是,如果某項(xiàng)業(yè)務(wù)需要長(zhǎng)期的演進(jìn),就需要大量的迭代計(jì)算。這些計(jì)算很難直接在原始數(shù)據(jù)上進(jìn)行,所以將原始數(shù)據(jù)保存為向量既是一種規(guī)范(即進(jìn)行特征與計(jì)算機(jī)語(yǔ)言的轉(zhuǎn)譯),同時(shí)也是迭代計(jì)算的前提。其重要性和必要性應(yīng)該無(wú)需再進(jìn)行討論。
在當(dāng)前的工作中,我恰好需要對(duì)文本進(jìn)行更深入的處理 ,所以借著機(jī)會(huì),邊做邊梳理。
PS: 大約在幾年前,甚至我開(kāi)始用Bert時(shí),我并沒(méi)有意識(shí)到這個(gè)領(lǐng)域的突破會(huì)如此具有開(kāi)創(chuàng)性,我認(rèn)為至少相當(dāng)長(zhǎng)一段水平,這個(gè)領(lǐng)域會(huì)保持在差不多的狀態(tài)。所以過(guò)去我一直是抱著以用為主,適當(dāng)了解的心態(tài)做這個(gè)領(lǐng)域的事?,F(xiàn)在這個(gè)觀念轉(zhuǎn)變了,以transformer結(jié)構(gòu)為基礎(chǔ)的深度學(xué)習(xí)已經(jīng)證明可以做到足夠商業(yè)化的效果。對(duì)這個(gè)領(lǐng)域進(jìn)行深入的研究和開(kāi)發(fā)–即使是從現(xiàn)在開(kāi)始–也會(huì)帶來(lái)巨大的收益。
內(nèi)容
1 word2vec
早前有整理過(guò)word2vec的一些資料(之后要review),這里只是把基本的概念稍微強(qiáng)化一下。
首先,在2018年以前,word2vec還算是比較主流的,是不是bert的出現(xiàn)撼動(dòng)了它的地位?
其次,word2vec的思想,也就是將單詞轉(zhuǎn)為向量,且可進(jìn)行語(yǔ)義度量(近義詞距離近,反義詞距離遠(yuǎn))應(yīng)該還是一個(gè)比較不變的。
2 m3e
先來(lái)看一些實(shí)際的使用/測(cè)試結(jié)果。
首先下載模型包 m3e-base
然后安裝一個(gè)包!pip3 install sentence_transformers -i https://mirrors.aliyun.com/pypi/simple/
假設(shè)我現(xiàn)在有3個(gè)query和3個(gè)answer,我需要對(duì)它們分別進(jìn)行轉(zhuǎn)換,然后計(jì)算他們之前的距離(相似度)。
先導(dǎo)入包,并對(duì)查詢進(jìn)行向量轉(zhuǎn)換
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('m3e-base',cache_folder="./m3e-base")
questions = ['存文件', '讀取文件','排序']
embeddings = model.encode(questions)
array([[-0.04188242, 0.8120958 , 0.75951165, ..., -0.7462918 ,
-0.37102422, -1.1583616 ],
[-0.18861888, 0.45038733, 0.7915643 , ..., -0.8806971 ,
-0.35007524, -0.9292178 ],
[-0.3631838 , 0.10787209, 0.55987704, ..., -1.1905028 ,
-1.2330985 , -0.6836705 ]], dtype=float32)
然后對(duì)答案也進(jìn)行向量轉(zhuǎn)換
answer1 = '''import json
# 文件的路徑+名稱 -> json字符串
def read_file_as_json(fpath = None ):
# 讀取一個(gè)文件
with open(fpath, 'r') as f:
content = f.read()
json_content = json.dumps(content)
return json_content'''
answer2 = '''import json
# 存一個(gè)文件
def write_json_to_file(json_str = None, fpath = None):
rev_content = json.loads(json_str)
with open(fpath, 'w') as f:
f.write(rev_content)
return True
'''
answer3 = '''import json
# 字符串到文件
def write_str_to_file(some_str = None, fpath = None):
json_str = json.dumps(some_str)
write_json_to_file(json_str, fpath)
'''
answers = [answer1, answer2, answer3]
res_embeddings = model.encode(answers)
假設(shè)現(xiàn)在計(jì)算最符合第一個(gè)查詢的結(jié)果,對(duì)應(yīng)的向量是embeddings[0], 對(duì)應(yīng)的原始查詢是「存文件」
import numpy as np
# 計(jì)算歐幾里得距離
distance1 = np.linalg.norm(embeddings[0] - res_embeddings[0])
distance2 = np.linalg.norm(embeddings[0] - res_embeddings[1])
distance3 = np.linalg.norm(embeddings[0] - res_embeddings[2])
print("歐幾里得距離:", distance1,distance2, distance3 )
歐幾里得距離: 14.339659 12.545303 13.624656
看起來(lái)與第二個(gè)答案的向量最為相似,里面有’存…文件’的類似文本。
如果要并發(fā)執(zhí)行距離計(jì)算,那么通過(guò)
# 一次計(jì)算歐式距離
distances = np.linalg.norm(embeddings[0]-res_embeddings, axis=1)
array([14.339659, 12.545303, 13.624656], dtype=float32)
如果answer集合的向量特別大,那么可以通過(guò)類似milvus這樣的向量數(shù)據(jù)庫(kù)完成查詢。其實(shí)本質(zhì)上也就是分布式并行計(jì)算向量。
這算是完成向量轉(zhuǎn)換與相似性計(jì)算的使用驗(yàn)證。
整體上說(shuō)使用還算是比較簡(jiǎn)單,效果也不錯(cuò)。但是在使用GPU計(jì)算上比較麻煩,反正我按說(shuō)明試著搞了一下,但是還是沒(méi)成功,也就算了。實(shí)際在使用CPU計(jì)算速度也還可以,這倒是說(shuō)明了向量轉(zhuǎn)換這步的計(jì)算開(kāi)銷并沒(méi)有那么大,在工業(yè)量產(chǎn)上是可行的。未來(lái)自己再開(kāi)發(fā)一版模型就好了。
再測(cè)試一下對(duì)于長(zhǎng)本文的支持,以下是一段測(cè)試文本,長(zhǎng)度大約為1000
test_txt ='''根據(jù)提供的接口文檔,ESIO(Elastic Search IO)是用于存儲(chǔ)和搜索元數(shù)據(jù)的服務(wù),它提供了一系列接口來(lái)執(zhí)行數(shù)據(jù)的存儲(chǔ)、查詢和刪除操作。以下是文檔中提到的主要接口和功能的簡(jiǎn)要說(shuō)明:
### 1. 功能簡(jiǎn)述
ESIO提供數(shù)據(jù)的存儲(chǔ)和查詢功能,主要包括以下操作:
- 連通性測(cè)試:通過(guò)訪問(wèn)`/info/`接口來(lái)測(cè)試服務(wù)是否可用。
- 狀態(tài)檢查:通過(guò)訪問(wèn)`/stat/`接口查看當(dāng)前數(shù)據(jù)庫(kù)的狀態(tài)。
- 存一條記錄:通過(guò)`/save_a_rec/`接口存儲(chǔ)一條記錄。
- 匹配查詢:使用詞進(jìn)行匹配查詢,例如按照"first_name"字段進(jìn)行匹配查詢。
- 短語(yǔ)查詢:使用短語(yǔ)進(jìn)行查詢,例如要同時(shí)滿足"rock climbing"短語(yǔ)存在才返回。
### 2. 部署方式
ESIO依賴ES數(shù)據(jù)庫(kù),使用一鍵部署方式部署。您可以執(zhí)行提供的腳本來(lái)完成部署。請(qǐng)注意,文檔中提供的腳本可能已經(jīng)過(guò)時(shí),因此建議根據(jù)實(shí)際情況進(jìn)行調(diào)整。
### 3. 請(qǐng)求說(shuō)明
文檔中提供了不同類型的請(qǐng)求說(shuō)明,包括:
- 連通性測(cè)試:通過(guò)訪問(wèn)`/info/`接口來(lái)測(cè)試連接。
- 狀態(tài)檢查:通過(guò)訪問(wèn)`/stat/`接口查看數(shù)據(jù)庫(kù)狀態(tài)。
- 存儲(chǔ)記錄:使用`/save_a_rec/`接口存儲(chǔ)一條記錄。
- 匹配查詢:使用詞進(jìn)行匹配查詢,例如按照"first_name"字段進(jìn)行匹配查詢。
- 短語(yǔ)查詢:使用短語(yǔ)進(jìn)行查詢,例如要同時(shí)滿足"rock climbing"短語(yǔ)存在才返回。
### 4. v1.1 豐富增刪改查
文檔還提到了v1.1版本的功能增強(qiáng),包括更多的增刪改查操作。這些操作包括:
- 創(chuàng)建記錄:使用`create_a_rec_with_id`接口以主鍵存入一條記錄。
- 覆蓋存入記錄:使用`index_a_rec_with_id`接口以主鍵覆蓋存入一條記錄。
- 刪除記錄:使用`del_a_rec_with_id`接口以主鍵刪除一條記錄。
- 獲取記錄:使用`get_a_rec_with_id`接口獲取一條記錄。
- 更新記錄:使用`update_a_rec_with_id`接口以主鍵覆蓋更新一條記錄。
- 批量操作:使用`bulk_opr`接口實(shí)現(xiàn)批量操作,包括創(chuàng)建、刪除、更新和索引。
- 批量獲?。菏褂胉mget_with_id_list`接口根據(jù)id批量獲取記錄。
這些功能可以幫助用戶進(jìn)行更靈活的數(shù)據(jù)操作和管理。
請(qǐng)注意,具體的請(qǐng)求和響應(yīng)參數(shù)需要根據(jù)實(shí)際使用情況和數(shù)據(jù)結(jié)構(gòu)進(jìn)行配置。文檔中提供了一些示例代碼,可供參考。'''
在原始長(zhǎng)度上乘以200倍,大約為20萬(wàn)字。
embeddings = model.encode([test_txt*200])
結(jié)果是仍然可以很快轉(zhuǎn)為768維向量,測(cè)試結(jié)束。
3 關(guān)于m3e
既然m3e的向量化可以滿足需求,這時(shí)候不妨回過(guò)頭看看這個(gè)模型本身的信息。
可參考文章1
發(fā)布的公司:似乎是一家叫聞達(dá)的公司
適用的場(chǎng)景:主要是中文,少量英文的情況,支持文本相似度、文本檢索
版本:base版的相對(duì)好
關(guān)于幾類任務(wù)的表現(xiàn)
就目前而言,我更關(guān)注s2s的應(yīng)用:相似度、重復(fù)、分類。
注意,這里提到了m3e是基于bert模型訓(xùn)練的。
聯(lián)想到m3e的向量長(zhǎng)度是768,和Bert一致,這說(shuō)明了在這個(gè)長(zhǎng)度上,應(yīng)當(dāng)足以應(yīng)對(duì)一般的語(yǔ)言問(wèn)題。當(dāng)然,如果條件允許的話,用更長(zhǎng)的向量應(yīng)該也是ok的。768或者可以被視為一個(gè)下限。
另外transformer架構(gòu)是成功的,bert和gpt兩個(gè)家族都是基于這個(gè)基礎(chǔ)架構(gòu)衍生出來(lái)的。所以,我可以從這個(gè)架構(gòu)開(kāi)始仔細(xì)的研究,從頭開(kāi)始做。
現(xiàn)在重頭開(kāi)始做的最大好處是很多路都有人探過(guò)了,資料很多,不會(huì)走歪路。另外就是算力供應(yīng)還算是不錯(cuò),現(xiàn)在可以租算力搞。
另外,Bert家族的模型證明是比較輕的,容易并行化、工業(yè)化;GPT家族相對(duì)重一些,但是在一個(gè)較小的參數(shù)空間上也是可行的,例如6B模型,個(gè)人完全也可以搞。
最大的工作和價(jià)值則是在于:能不能對(duì)attention方法進(jìn)行更有效的設(shè)計(jì);能不能采用類似ensemable的方法,訓(xùn)練較多的小模型綜合增強(qiáng);能不能采用一個(gè)體系來(lái)進(jìn)行流水線的生成;能不能采用更好的agent來(lái)進(jìn)行自我調(diào)節(jié)。
所以下一步,就是開(kāi)始實(shí)踐,目標(biāo)是可以完全自主的重頭搭建完全可控的Bert和GPT類模型。
4 部署
4.1 本地鏡像
目前本地算力不是那么充足,不過(guò)我還是將其作為一項(xiàng)基本任務(wù)將其完成,這樣未來(lái)萬(wàn)一需要長(zhǎng)期部署的時(shí)候可以省事。
在已有的pytorch鏡像上修改,考入模型文件并建立tornado服務(wù)的文件
m3e-base server.py server_funcs.py
server.py
from server_funcs import *
import tornado.httpserver # http服務(wù)器
import tornado.ioloop # ?
import tornado.options # 指定服務(wù)端口和路徑解析
import tornado.web # web模塊
from tornado.options import define, options
import os.path # 獲取和生成template文件路徑
# 引入模型
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('m3e-base',cache_folder="./m3e-base")
app_list = []
IndexHandler_path = r'/'
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.write('【GET】This is Website for Internal API System')
self.write('Please Refer to API document')
print('Get got a request test')
# print(buffer_dict)
def post(self):
request_body = self.request.body
print('Trying Decode Json')
some_dict = json.loads(request_body)
print(some_dict)
msg_dict = {}
msg_dict['info'] = '【POST】This is Website for Internal API System'
msg_dict['input_dict'] = some_dict
self.write(json.dumps(msg_dict))
print('Post got a request test')
IndexHandler_tuple = (IndexHandler_path,IndexHandler)
app_list.append(IndexHandler_tuple)
M3EHandler_path = r'/m3e/'
class M3EHandler(tornado.web.RequestHandler):
def get(self):
self.write('【GET】文本向量化')
self.write('Please Refer to API document')
print('Get got a request test')
def post(self):
request_body = self.request.body
input_list = json.loads(request_body)
res_list = model.encode(input_list)
self.write(json.dumps(res_list.tolist()))
M3EHandler_tuple = (M3EHandler_path,M3EHandler)
app_list.append(M3EHandler_tuple)
if __name__ == '__main__':
#
tornado.options.parse_command_line()
apps = tornado.web.Application(app_list, **settings)
http_server = tornado.httpserver.HTTPServer(apps)
define('port', default=8000, help='run on the given port', type=int)
http_server.listen(options.port)
# 單核
# 多核打開(kāi)注釋
# 0 是全部核
# http_server.start(num_processes=10) # tornado將按照cpu核數(shù)來(lái)fork進(jìn)程
# ---啟動(dòng)
print('Server Started')
tornado.ioloop.IOLoop.instance().start()
server_funcs.py
# 保留,用于提供server.py的引入
# 【引入時(shí)處于和GlobalFunc同級(jí)別的位置】
import json
from json import JSONEncoder
class MyEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, np.integer):
return int(obj)
elif isinstance(obj, np.floating):
return float(obj)
elif isinstance(obj, np.ndarray):
return obj.tolist()
if isinstance(obj, datetime):
return obj.__str__()
if isinstance(obj, dd.timedelta):
return obj.__str__()
else:
return super(MyEncoder, self).default(obj)
# 【創(chuàng)建tornado所需問(wèn)文件夾】
import os
# 如果路徑不存在則創(chuàng)建
def create_folder_if_notexist(somepath):
if not os.path.exists(somepath):
os.makedirs(somepath)
return True
m_static = os.path.join(os.getcwd(),'m_static')
m_template = os.path.join(os.getcwd(),'m_template')
create_folder_if_notexist(m_static)
create_folder_if_notexist(m_template)
settings = {
'static_path':m_static,
'template_path':m_template
}
調(diào)用
import requests as req
questions = ['存文件', '讀取文件','排序']
res_list = req.post('http://127.0.0.1:8000/m3e/',json = questions).json()
import numpy as np
res_arr = np.array(res_list)
這樣就完成了鏡像準(zhǔn)備,以后要用的時(shí)候起一個(gè)服務(wù)就可以了,這個(gè)服務(wù)只有一個(gè)功能:輸入文本列表,返回向量列表。
4.2 外部部署
如果短時(shí)間有大量的需求,我更情愿使用彈性的算力來(lái)解決問(wèn)題。
用自己的鏡像打開(kāi)一個(gè)新容器,root用戶的秘鑰已經(jīng)允許m0主機(jī)和算網(wǎng)機(jī)登錄,這樣方便從m7拷貝模型文件過(guò)去。
其中 /root/autodl-tmp 是系統(tǒng)盤(pán)免費(fèi)30G,/root/autodl-pub 是數(shù)據(jù)盤(pán),免費(fèi)50G。
ps: 我發(fā)現(xiàn)要用pytorch2.0的容器創(chuàng)建,所以之前的那個(gè)鏡像不能用
步驟0:修改ssh
在.ssh下面增加秘鑰,增加后即刻生效,然后立即禁用密碼登錄
vim /etc/ssh/sshd_config
PermitRootLogin prohibit-password
MaxAuthTries 6
ClientAliveInterval 60
ClientAliveCountMax 10
PasswordAuthentication no
RSAAuthentication yes
PubkeyAuthentication yes
MaxSessions 10
步驟1:將數(shù)據(jù)從m7拷貝到/root/autodl-tmp/
rsync -rvltz -e 'ssh -p 18045' --progress /data/model_file/m3e-base root@connect.southb.gpuhub.com:/root/autodl-tmp/
步驟2:安裝包pip3 install sentence_transformers -i https://mirrors.aliyun.com/pypi/simple/
步驟3:將server.py和server_funcs.py也拷貝到/root/autodl-tmp/目錄下。
步驟4:?jiǎn)?dòng)服務(wù)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-831315.html
┌─root@autodl-container-824347b459-10803d25:~/autodl-tmp
└─ $ python3 server.py
Server Started
autodl給了一個(gè)jupyter,可以測(cè)試
鏡像是可以保存的,而且非常厚道,只算用戶額外掛載的私有數(shù)據(jù),等下次真的需要的時(shí)候再保存。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-831315.html
到了這里,關(guān)于Python 文本處理和語(yǔ)義分析2 使用m3e對(duì)文本向量化的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!