Practical Applications of Chatbot

Transformer

Text Analysis

Natural Language Processing

文本分析基本任務: 斷詞、詞性標記、實體辨識 

"我和順仔早上5點登上「福建號」"

我和<person>順仔</person>早上5點登上「<ship>福建號</ship>

斷詞(tagger)

(命名)實體辨識
name-entity-recognition

原文

我  和  順仔  早上  5點   登上   福建號

          r   c    nr        t        t         v        nr

詞性標記
part-of-speech

專業術語 + 分類

應用:索引、Hyperlink

資料來源:DocuSky 數位人文學術研究平臺:視覺化與統計分析,2020數位人文培訓營

文本分析統計應用文字雲 

統計單元:斷詞(實體辨識)的結果

文本分析常見任務

情感分析(Sentiment Analysis)

負面評價Negative

這家店的售後服務完全不行

餐點還不錯啦,但是還有改進空間

中性評價neutral

中性評價neutral

正面評價positive

很讚喔,超喜歡你們家的產品

分類問題

情感分析

文本分析常見任務

編碼

輸入層

隱藏層

輸出層

輸出結果分類

真實

政治宣傳

惡搞

反諷

假新聞

帶風向

片面資訊

新聞分類

分類問題

文本分析常見任務

機器翻譯

迴歸問題

Transformer

輸入英文: milk drink I

輸出法文: Je bois du lait <eos>

編碼器

解碼器

編碼器輸出

 

解碼器輸入

<sos>: 開始符號

<eos>: 結束符號

文本分析常見任務

文本生成

輸入: 一句話

輸出: 一篇短文!

經過多個語言模型...

迴歸問題

機器學習/深度學習

訓練資料

❶¹ 前處理

🅐 訓練集

🅑 驗證集

🅒 測試集

❷ 訓練參數

決定最終預測模型

❸ 產生預測用模型

➍ 測試模型

特徵萃取

❶² 資料集分割

文本分析任務資料集問題

1. 訓練資料很難取得

文本分析任務資料集問題

遷移學習(transfer learning): 研究者可以專注於目標領域資料集

機器學習傳統做法

資料集1

資料集2

資料集3

任務1

任務2

任務3

Transfer Learning

來源領域任務

資料集S

來源領域知識

目標領域任務

大量

資料集T

小量

預訓練

兩階段

2. 需進行編碼: 

文本分析任務編碼問題

訓練模型擅長直接處理數值資料

[編碼方法1] One-hot encoding: 一字一編號

<SOS> I played the piano

1

2

3

4

5

6

7

8

編碼

輸入層

隱藏層

輸出層

以向量表示

編號

詞彙

<SOS>

編號

0     0      0      0      0      0      0      0  

文本分析任務編碼問題

One-hot encoding問題: 詞彙很多(資料稀疏)、編碼與語意無關...

dog

文本分析的詞彙很多

字詞編碼:向量很長、資料稀疏

 向量長度 =詞彙數量(10000)

one-hot encoding

文本分析任務編碼問題

One-hot encoding問題: 詞彙很多(資料稀疏)、編碼與語意無關...

one-hot encoding

字彙語意相近,不一定有比較近的「距離」

近?

遠?

Coffee跟Laptop的距離比Tea近?

文本分析任務編碼問題

[編碼方法2]: Word Embedding(詞嵌入)

字彙語意相近,編碼必須給予比較近的「距離」

文本分析任務編碼問題

[編碼方法2]: 常見的Word Embedding方法—Word2Vec

Word2Vec學習大量詞彙,將字詞對應到100-300維度的空間

100-300

Word Embedding

深度學習文字任務Word Embedding演算法

Word Embedding

詞彙

7D to 2D

7D to 2D

維度

Word2Vec

文本分析任務脈絡(context)

Word Embedding就是編碼問題的解答嗎?

千金散盡還復來

千金最近好嗎?

他向來一諾千金,說到做到

同樣的詞 會有相同的 詞嵌入

But...

同樣的詞,不同前後文,經常有不同意涵

脈絡(Context)的影響

Transformer

Vaswani et al., Attention is All You Need https://arxiv.org/abs/1706.03762

Transformers模型

自注意(self-attention)機制

Encoder

Decoder

Attention Layer

Attention Layer

Attention Layer

語言模型類型

Encoder: read a sentence, understand its meaning, and convert it into an internal format (a bunch of numbers) that the machine can manipulate.

Decoder: take the encoded English sentence and turns it into a French sentence. It generates the translation word by word.

Translator

Creator

Building Blocks of LLM(1/3)

Building Blocks of LLM(2/3)

Building Blocks of LLM(3/3)

Tokenization & Embedding

tokens

Tokenization & Embedding

I love programming.

['I', ' love', ' programming', '.']

['I', ' love', ' pro', 'gram', 'ming', '.']

[72, 104, 3562, 4]

Tokenization & Embedding

pip install -U transformers torch
from transformers import BertTokenizer, BertModel
import torch
# Load pre-trained model and tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')

Tokenizer of BERT(1/3)

2. Load Pre-trained Model and Tokenizer

1. Import Libraries

# Define the text
text = "The quick brown fox jumps over the lazy dog."
# Tokenize the text, 'pt' means PyTorch format
inputs = tokenizer(text, return_tensors='pt')

 3.Define and Tokenize Text

# Obtain the embeddings
with torch.no_grad():
    outputs = model(**inputs)
# Extract the last hidden state (embeddings)
last_hidden_states = outputs.last_hidden_state

4. Obtain Embeddings

Tokenizer of BERT(2/3)

# Print the dimensions of the embeddings
print("Shape of the last hidden state (embeddings):", 
      last_hidden_states.shape)

# Print embeddings for each token along with their vector dimension
tokens = tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])
for token, embedding in zip(tokens, last_hidden_states[0]):
    print(f"Token: {token}, Embedding Dimension: {embedding.shape}, 
          Embedding (first 5 components): {embedding[:5]}...")  
          # Display first 5 components for brevity

 5. Print Embeddings

Tokenizer of BERT(3/3)

from transformers import BertTokenizer, BertModel
import torch

# Load pre-trained model and tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')

# Define the text
text = "The quick brown fox jumps over the lazy dog."

# Tokenize the text
inputs = tokenizer(text, return_tensors='pt')

# Obtain the embeddings
with torch.no_grad():
    outputs = model(**inputs)

# Extract the last hidden state (embeddings)
last_hidden_states = outputs.last_hidden_state

# Print the dimensions of the embeddings
print("Shape of the last hidden state (embeddings):", last_hidden_states.shape)

# Print embeddings for each token along with their vector dimension
tokens = tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])
for token, embedding in zip(tokens, last_hidden_states[0]):
    print(f"Token: {token}, Embedding Dimension: {embedding.shape}, Embedding (first 5 components): {embedding[:5]}...") 
    # Display first 5 components for brevity

free embedding model: Bert model範例(完整)

''' this is not a module '''
import os
import pandas as pd
import tiktoken
from openai import OpenAI

client = OpenAI()  # OpenAI API client,須設定環境變數OPENAI_API_KEY

#
# 計算embedding vector
#
def get_embedding(text, model="text-embedding-3-small"):
    '''呼叫OpenAI API, 計算embedding vector
       text: 要計算embedding vector的字串
       model: embedding model, 預設值text-embedding-3-small, 其他選項參考OpenAI官網
    '''

    text = text.replace("\n", " ")    # 置換「換行字元」為空白字元
    return client.embeddings.create(input=[text], model=model).data[0].embedding

# 範例主程式
# 
# 1. 讀取資料
absolute_path = os.path.dirname(__file__)  # 此py檔案的絕對路徑
FILENAME = "專題.csv"              # 資料檔,共有10個欄位
file_path = os.path.join(absolute_path, FILENAME)  # 組成資料檔的絕對路徑

df = pd.read_csv(file_path, index_col=False)   # 讀取資料,建立data frame

df = df[["Q", "A"]] # 挑取Q, Al兩個欄位
df = df.dropna()    # 資料清理

print(df.head(2))   # 印出前2筆,看看是否正確

#df.drop("Time", axis=1, inplace=True)

EMBEDDING_MODEL = "text-embedding-3-small"
EMBEDDING_ENCODING = "cl100k_base"
MAX_TOKENS = 8000  # the maximum for text-embedding-3-small is 8191

encoding = tiktoken.get_encoding(EMBEDDING_ENCODING)

# omit reviews that are too long to embed
#df["n_tokens"] = df.combined.apply(lambda x: len(encoding.encode(x)))
df["n_tokens"] = df.Q.apply(lambda x: len(encoding.encode(x)))
df = df[df.n_tokens <= MAX_TOKENS]
print(len(df))

###  Get embeddings and save them for future reuse
df["embedding"] = df.Q.apply(lambda x: get_embedding(x, model=EMBEDDING_MODEL))
df.to_csv('review_short_embeddings.csv')

a = get_embedding("hi", model=EMBEDDING_MODEL)
print(f'embedding for "hi":{a}')

OpenAI embedding範例: 建立知識集向量、存檔(1/2)

import os
import pandas as pd
from ast import literal_eval
import tiktoken
from openai import OpenAI
from scipy.spatial.distance import cosine

client = OpenAI()  # OpenAI API client,須設定環境變數OPENAI_API_KEY

#
# 計算embedding vector
#
def get_embedding(text, model="text-embedding-3-small"):
    '''呼叫OpenAI API, 計算embedding vector
       text: 要計算embedding vector的字串
       model: embedding model, 預設值text-embedding-3-small, 其他選項參考OpenAI官網
    '''

    text = text.replace("\n", " ")    # 置換「換行字元」為空白字元
    return client.embeddings.create(input=[text], model=model).data[0].embedding

#
# 讀取知識庫含embedding vector。
#
def get_knowledgebase_with_embedding(file_path):
    df = pd.read_csv(file_path, index_col=0) #讀取資料
    # print(df.head(2))
    return df

#
# 計算embedding vector之間的距離
#
def cosine_distance(v_a, v_b):
    value = cosine(v_a, v_b)
    return value

#--------------------------------------------    
# 範例主程式:
# 新進問題 與 知識庫中儲存問題相比較
# 逐一比對後,將embedding向量相近者對應之「答案」取出
#-------------------------------------------- 

# 0. 前置作業讀取資料與向量檔案
EMBEDDING_MODEL = "text-embedding-3-small"
filename = 'review_short_embeddings.csv'  # 範例檔欄位 ,Q,A,n_tokens,embedding
absolute_path = os.path.dirname(__file__)  # 此py檔案的絕對路徑
file_path = os.path.join(absolute_path, filename)  # 組成資料檔的絕對路徑
df = get_knowledgebase_with_embedding(file_path)   # 讀取檔案存入dataframe 

# 1. 使用者輸入問題後,將問題與知識庫的向量做比對
question = "如何取件?"  # 測試用的新進問題
v_question = get_embedding(question, model=EMBEDDING_MODEL)
# 比較v_question與 df中所有問題的向量
# 呼叫literal_eval()是因為當初寫出檔案時,已將浮點陣列[]變成字串'[]',故此處再轉回來
df["distance"] = df.embedding.apply(lambda x: cosine_distance(literal_eval(x), v_question))

# 1.1排序
df2 = df.sort_values(by=['distance'], ascending=True)
print(df2.head(2))

# 2. 生成
# 2.1 OpenAI參數設定
model = "gpt-3.5-turbo"     # openai model
temperature = 0           # 回應變化的多樣性,0:變化最少, 1:變化最多
max_tokens = 200            # 最多200個tokens

# 2.2 問題設定(few-shot prompting)
num_of_shot = 3             # few-shot prompting(生成時參考的案例數)
instruction ="請依下列內容, 重整後產生答案。"
messages = [               
    {
        "role": "system",
        "content": instruction
    }
]           

candidate = df2.iloc[0:num_of_shot, lambda df2:[0,1]]   # 挑出Q,A兩欄, 前num_of_shot名,作為prompt候選內容
print(f'攫取的問答:{candidate.values.tolist()}')
# few-shot prompting, 依序加上前例
for item in candidate.values.tolist():
    prompt_u = {
        "role": "user",
        "content": item[0]
    }
    messages.append(prompt_u)
    prompt_a = {
        "role": 'assistant',
        "content": item[1]
    }
    messages.append(prompt_a) # 依序加在其後, 要留意max token of context的限制
# 2.3 加上問題
messages.append({
    "role": "user",
    "content": question
})

print('組成的訊息',messages)
# 2.4 生成
response = client.chat.completions.create(
    model = model,
    messages = messages,
    temperature= temperature,
    max_tokens=max_tokens
)
print(response.choices[0].message.content)

OpenAI embedding範例: 比對問題與知識集向量(2/2)

Ckip Transformers

Ckip Transformers安裝與使用

Ckip Transformers安裝與使用

使用方法ㄧ: 透過HuggingFace Transformers

圖片來源:Transformers, PyTorch, TensorFlow

pip install -U transformers

❶ 安裝transformers套件

❷ 安裝深度學習套件(擇一)

pip install -U torch
pip install -U tensorflow

* 後續以PyTorch為例

from ckip_transformers.nlp import CkipWordSegmenter, CkipPosTagger, CkipNerChunker
## 將斷詞結果words與 part-of-speech結果pos打包在一起
def pack_ws_pos_sentece(words, pos):
   assert len(words) == len(pos), f'words{len(words)}和pos{len(pos)}長度必須一樣'
   result = []   # 最終結果串列
   for word, p in zip(words, pos):  # zip將words,pos對應位置鏈在一起
      result.append(f"{word}({p})")
   return "\u3000".join(result)  #\u3000是全形空白字元

# Input text
text = [
   "華氏80度,氣壓30-6,整天下雨。前進到奇武荖,聆聽朗誦。然後珍珠里簡和冬瓜山,晚間在珍珠里簡的禮拜有一百五十人,拔了七十六顆牙。",
   "相當涼爽舒適,與阿華出去鄉間拜訪他的一位老朋友,是一位農人。我們被看見,認出來,且受排斥。遭到兩隻大黑狗攻擊,孩童吼叫,狂暴的辱罵。 我們回家,在吃過飯之後,我們比過去更勤奮的研讀各自的功課。",
   "華氏84度,陰天,氣壓30-10。上午七點離開大里簡,大部分都用走的,上午時點三十分來到頭城,拔了一些牙。十一點到打馬煙,我們剛到雨就來了。禮德醫師在路上幫一個傢伙縫手指。新漆好的禮拜堂到處都是用藍色和紅色漆,全部由他們自費花了十二元。禮德醫師拍團體照。我們照顧了十四名病人,還拔了一些牙。主持聖餐禮,聆聽十七個人背誦。大家都做得很好,我們發禮物給他們。"
   ]

ws_driver = CkipWordSegmenter(model="bert-base") # 載入 斷詞模型
pos_driver = CkipPosTagger(model="bert-base")    # 載入 POS模型
ner_driver = CkipNerChunker(model="bert-base")   # 載入 實體辨識模型
print('all loaded...')

# 執行pipeline 產生結果
ws = ws_driver(text)   # 斷詞
pos = pos_driver(ws)   # 詞性: 注意斷詞-詞性是依序完成
ner = ner_driver(text) # 實體辨識

##列印結果
for sentence, word, p, n in zip(text, ws, pos, ner):
    print(sentence)
    print(pack_ws_pos_sentece(word, p))
    for token in n:
        print(f"({token.idx[0]}, {token.idx[1]}, '{token.ner}', '{token.word}')")
    print()

華氏80度,氣壓30-6,整天下雨。前進到奇武荖,聆聽朗誦。然後珍珠里簡和冬瓜山,晚間在珍珠里簡的禮拜有一百五十人,拔了七十六顆牙。


華氏(Na) 80(Neu) 度(Nf) ,(COMMACATEGORY) 氣壓(Na) 30-6(Neu) ,(COMMACATEGORY) 整(Neqa) 天(Nf) 下雨(VA) 。(PERIODCATEGORY) 前進(VA) 到(P) 奇武荖(Nc) ,(COMMACATEGORY) 聆聽(VC) 朗誦(VC) 。(PERIODCATEGORY) 然後(D) 珍珠里簡(Na) 和(Caa) 冬瓜山(Nc) ,(COMMACATEGORY) 晚間(Nd) 在(P) 珍珠里簡(Nc) 的(DE) 禮拜(Na) 有(V_2) 一百五十(Neu) 人(Na) ,(COMMACATEGORY) 拔(VC) 了(Di) 七十六(Neu) 顆(Nf) 牙(Na) 。(PERIODCATEGORY)


(0, 5, 'QUANTITY', '華氏80度')
(8, 12, 'QUANTITY', '30-6')
(37, 40, 'LOC', '冬瓜山')
(41, 43, 'TIME', '晚間')
(52, 56, 'CARDINAL', '一百五十')
(60, 63, 'CARDINAL', '七十六')

實體辨識

11類專有名詞

7類數量詞

Part-of-Speech(POS)

61種詞性

華氏80度,氣壓30-6,整天下雨。前進到奇武荖,聆聽朗誦。然後珍珠里簡和冬瓜山,晚間在珍珠里簡的禮拜有一百五十人,拔了七十六顆牙。


華氏(Na) 80(Neu) 度(Nf) ,(COMMACATEGORY) 氣壓(Na) 30-6(Neu) ,(COMMACATEGORY) 整(Neqa) 天(Nf) 下雨(VA) 。(PERIODCATEGORY) 前進(VA) 到(P) 奇武荖(Nc) ,(COMMACATEGORY) 聆聽(VC) 朗誦(VC) 。(PERIODCATEGORY) 然後(D) 珍珠里簡(Na) 和(Caa) 冬瓜山(Nc) ,(COMMACATEGORY) 晚間(Nd) 在(P) 珍珠里簡(Nc) 的(DE) 禮拜(Na) 有(V_2) 一百五十(Neu) 人(Na) ,(COMMACATEGORY) 拔(VC) 了(Di) 七十六(Neu) 顆(Nf) 牙(Na) 。(PERIODCATEGORY)


(0, 5, 'QUANTITY', '華氏80度')
(8, 12, 'QUANTITY', '30-6')
(37, 40, 'LOC', '冬瓜山')
(41, 43, 'TIME', '晚間')
(52, 56, 'CARDINAL', '一百五十')
(60, 63, 'CARDINAL', '七十六')

只辨識出1個實體

且分類錯誤

應為GPE

3個地名,共出現四次

珍珠里簡: 不同詞性?

Na:普通名詞,Nc: 地方詞

Introduction to Transformer, Tokenizer and Embedding

By Leuo-Hong Wang

Introduction to Transformer, Tokenizer and Embedding

  • 113