2024年1月11日 星期四

Azure 學習筆記 : Azure AI 服務之文字分析 (一) 語言偵測

Azure AI 服務為原 Azure 認知服務 (Cognitive Service) 與 OpenAI 服務合併而成, 認知服務的 Decision, Language, Speech, 與 Vision 功能不變, 但 API 版本不同, 主要內容如下 :
  • OpenAI : 透過 API 呼叫 OpenAI 的 LLM 模型
  • Decision : 異常偵測, 個人化工具, 內容仲裁等
  • Language : 文字分析, 語言理解, 翻譯工具, QnA 等
  • Speech : 文轉音, 音轉文, 語者辨識, 語音翻譯等
  • Vision : 電腦視覺, 臉孔辨識等
認知服務是微軟以 IBM 認知計算概念為基礎所提出的一種雲端服務, 讓服務開發者不需要具備豐富的機器學習與資料科學技能, 只要熟悉 REST API 用法即可輕鬆將 AI 認知智慧導入應用程式中. 

參考 :
 

本篇是我閱讀  "Python 人工智慧程式設計入門-使用 Microsoft Azure 雲端服務這本書的第 9 章 "文字分析" 的語言偵測 (language detection) 後所進行的實測紀錄. 

文字分析 (text analysis) 屬於 Azure AI 服務中 Language 功能, 它內建自然語言處理 (NLP) 功能可用來進行語言偵測, 情感分析, 關鍵字擷取, 與具名實體 (NER) 辨識等分析, 參考 :


認知服務的文字分析 API 教學文件參考 : 


應用程式範例放在 GitHub, 可參考不同程式語言之範例 :


舊版 (v3.0) 的文字分析 API 範例參考 :


書裡面的範例就是採用 v3.0 API. 


在前一篇測試中, 我們已經在 Azure 雲端建立了一個資源群組與一個執行個體, 藉此取得了資源的端點 (即服務網址) 與存取該資源所需之金鑰. 本篇則要在應用程式中用它們來存取已建立的 Azure 資源以便進行文字分析任務. 


一. 語言偵測 (Language detection) : 

此功能可以辨識出輸入文本是屬於哪一種人類語言, 此功能利用 Azure AI 的機器學習與 NLP 演算法辨識輸入的文本是屬於哪一種人類語言 (包含變體與方言), 這在設計支援多國語言的 AI 應用服務時很有用, 參考 : 


按 "快速入門" 超連結 :





底下有一個 "必要條件", 這些我們在前一篇中已完成, 透過建立資源群組與執行個體, 取得了資源的端點 (即網址) 與存取它所需之金鑰 : 




開發者可使用 C#, JAVA, JavaScript, 或 Python 等程式語言以 REST API 方式向 Azure AI 資源提出要求來取得回應. 點選 Python, 底下會出現使用 Python 存取文字分析資源的範例, 但必須先安裝 Azure AI 文字分析的用戶端套件 azure-ai-textanalytics : 

pip install azure-ai-textanalytics==5.2.0 

D:\python\test>pip install azure-ai-textanalytics==5.2.0    
Collecting azure-ai-textanalytics==5.2.0
  Downloading azure_ai_textanalytics-5.2.0-py3-none-any.whl (239 kB)
239.3/239.3 kB 1.0 MB/s eta 0:00:00
Collecting azure-core<2.0.0,>=1.24.0 (from azure-ai-textanalytics==5.2.0)
  Downloading azure_core-1.29.6-py3-none-any.whl.metadata (36 kB)
Collecting msrest>=0.7.0 (from azure-ai-textanalytics==5.2.0)
  Downloading msrest-0.7.1-py3-none-any.whl (85 kB)
85.4/85.4 kB 2.4 MB/s eta 0:00:00
Collecting azure-common~=1.1 (from azure-ai-textanalytics==5.2.0)
  Downloading azure_common-1.1.28-py2.py3-none-any.whl (14 kB)
Requirement already satisfied: typing-extensions>=4.0.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from azure-ai-textanalytics==5.2.0) (4.9.0)
Requirement already satisfied: anyio<5.0,>=3.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from azure-core<2.0.0,>=1.24.0->azure-ai-textanalytics==5.2.0) (3.7.1)
Requirement already satisfied: requests>=2.21.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from azure-core<2.0.0,>=1.24.0->azure-ai-textanalytics==5.2.0) (2.31.0)
Requirement already satisfied: six>=1.11.0 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from azure-core<2.0.0,>=1.24.0->azure-ai-textanalytics==5.2.0) (1.16.0)
Requirement already satisfied: certifi>=2017.4.17 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from msrest>=0.7.0->azure-ai-textanalytics==5.2.0) (2023.7.22)
Collecting isodate>=0.6.0 (from msrest>=0.7.0->azure-ai-textanalytics==5.2.0)
  Downloading isodate-0.6.1-py2.py3-none-any.whl (41 kB)
41.7/41.7 kB 2.1 MB/s eta 0:00:00
Requirement already satisfied: requests-oauthlib>=0.5.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from msrest>=0.7.0->azure-ai-textanalytics==5.2.0) (1.3.1)
Requirement already satisfied: idna>=2.8 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from anyio<5.0,>=3.0->azure-core<2.0.0,>=1.24.0->azure-ai-textanalytics==5.2.0) (3.4)
Requirement already satisfied: sniffio>=1.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from anyio<5.0,>=3.0->azure-core<2.0.0,>=1.24.0->azure-ai-textanalytics==5.2.0) (1.3.0)
Requirement already satisfied: exceptiongroup in c:\users\tony1\appdata\roaming\python\python310\site-packages (from anyio<5.0,>=3.0->azure-core<2.0.0,>=1.24.0->azure-ai-textanalytics==5.2.0) (1.1.3)
Requirement already satisfied: charset-normalizer<4,>=2 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from requests>=2.21.0->azure-core<2.0.0,>=1.24.0->azure-ai-textanalytics==5.2.0) (3.2.0)
Requirement already satisfied: urllib3<3,>=1.21.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from requests>=2.21.0->azure-core<2.0.0,>=1.24.0->azure-ai-textanalytics==5.2.0) (1.26.16)
Requirement already satisfied: oauthlib>=3.0.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from requests-oauthlib>=0.5.0->msrest>=0.7.0->azure-ai-textanalytics==5.2.0) (3.2.2)
Downloading azure_core-1.29.6-py3-none-any.whl (192 kB)
192.5/192.5 kB 2.9 MB/s eta 0:00:00
Installing collected packages: azure-common, isodate, azure-core, msrest, azure-ai-textanalytics
Successfully installed azure-ai-textanalytics-5.2.0 azure-common-1.1.28 azure-core-1.29.6 isodate-0.6.1 msrest-0.7.1

然後複製 "快速入門" 中的範例程式碼到編輯器 (我使用 Thonny), 將最前面兩列的 key 與 endpoint 變數換成自己的金鑰與端點網址, 然後存檔 (例如 azure_language_detect_1.py) : 

# azure_language_detect_1.py
key = "06f8a834tony4bdbbe264504b196606f"           # 此為範例金鑰
endpoint = "https://tony-test1.cognitiveservices.azure.com/"

from azure.ai.textanalytics import TextAnalyticsClient  
from azure.core.credentials import AzureKeyCredential  

# Authenticate the client using your key and endpoint 
def authenticate_client():
    ta_credential = AzureKeyCredential(key)
    text_analytics_client = TextAnalyticsClient(
            endpoint=endpoint, 
            credential=ta_credential)
    return text_analytics_client

client = authenticate_client()   # 傳回 TextAnalyticsClient 物件

# Example method for detecting the language of text
def language_detection_example(client):
    try:
        documents = ["Ce document est rédigé en Français."]
        response = client.detect_language(documents = documents, country_hint = 'us')[0]
        print("Language: ", response.primary_language.name)

    except Exception as err:
        print("Encountered exception. {}".format(err))

language_detection_example(client)

然後直接執行 :

>>> %Run azure_language_detect.py   
Language:  French  

可見得到正確輸出 (偵測到法語).

這個範例與書本上的舊版程式碼差很多, 上面的程式定義了兩個函式 : 
  • authenticate_client() : 
    利用所匯入的 AzureKeyCredential() 與 TextAnalyticsClient() 來驗證用戶端應用程式的存取權, 不管驗證是否成功, 它都會傳回一個 TextAnalyticsClient 物件.
  • language_detection_example() :
    呼叫傳回的 TextAnalyticsClient 物件之 detect_language() 方法偵測文本是哪種語言, 傳入參數 documents 為待測文本組成之串列 (可同時偵測多個不同語言之句子), 傳回一個 DetectLanguageResult 物件 (內容為一個兩層的字典), 偵測結果可用其primary_language.name 或 ['primary_language']['name'] 屬性取得.

以下是在互動環境中拆解執行步驟 : 

首先匯入函式 : 

>>> from azure.ai.textanalytics import TextAnalyticsClient  
>>> from azure.core.credentials import AzureKeyCredential  

接著定義認證用的金鑰與端點變數 : 

>>> key='06f8a834tony4bdbbe264504b196606f'        # 此為範例金鑰
>>> endpoint='https://tony-test1.cognitiveservices.azure.com/'  

然後呼叫 AzureKeyCredential() 並傳入 key 進行驗證, 它會傳回一個 AzureKeyCredential 物件 (驗證用) : 

>>> ta_credential=AzureKeyCredential(key)    
>>> type(ta_credential)    
<class 'azure.core.credentials.AzureKeyCredential'>    

接下來就可以呼叫 TextAnalyticsClient() 並傳入端點位址與 AzureKeyCredential 驗證物件, 它會傳回一個 TextAnalyticsClient 物件 (分析用) : 

>>> client=TextAnalyticsClient(endpoint=endpoint, credential=ta_credential)  
>>> type(client)  
<class 'azure.ai.textanalytics._text_analytics_client.TextAnalyticsClient'>    

注意, 不管金鑰與端點正確與否, 都會傳回一個 TextAnalyticsClient 物件, 但錯誤的金鑰或端點在後續做分析時會拋出例外. 

然後就可以建立一個待測句子串列 (可以一次偵測多個文本), 將它傳給 TextAnalyticsClient 物件的 detect_language() 測試, 傳回值是一個 DetectLanguageResult 物件 (類似字典) 組成的串列, 待測句子有幾個, 傳回串列裡就有幾個相對應的 DetectLanguageResult 物件, 先以上面的法文句子為例 :

>>> docs=['Ce document est rédigé en Français.']     # 待偵測之文本串列
>>> response=client.detect_language(documents=docs)    
>>> type(response)     
<class 'list'> 
>>> len(response)   
1
>>> type(response[0])    
<class 'azure.ai.textanalytics._models.DetectLanguageResult'>

可見傳入串列, 傳回值也是陣列, 裡面只有一個元素, 是一個類似字典的 DetectLanguageResult 物件, 查看 response 內容 : 

>>> response   
[DetectLanguageResult(id=0, primary_language=DetectedLanguage(name=French, iso6391_name=fr, confidence_score=1.0), warnings=[], statistics=None, is_error=False)]

可見偵測的結果放在 primary_language 這個 key 的值 (是一個 DetectedLanguage 物件) 裡面, 只要用 name 這個 key 即可取得偵測結果, 可以使用點運算子, 也可以使用 [] 運算子 : 

>>> response[0].primary_language.name       
'French'  
>>> response[0]['primary_language']['name']    
'French'

可以一次傳入多個句子去偵測, 例如下面中, 日, 韓, 英, 法五種語言六種文本的 '我愛你' : 

>>> docs=['我爱你', "Je t'aime", 'I love you', '我愛你', '愛してます', '사랑해요']    
>>> response=client.detect_language(documents=docs, country_hint = 'us')    
>>> response    
[DetectLanguageResult(id=0, primary_language=DetectedLanguage(name=Chinese_Simplified, iso6391_name=zh_chs, confidence_score=1.0), warnings=[], statistics=None, is_error=False), DetectLanguageResult(id=1, primary_language=DetectedLanguage(name=English, iso6391_name=en, confidence_score=1.0), warnings=[], statistics=None, is_error=False), DetectLanguageResult(id=2, primary_language=DetectedLanguage(name=English, iso6391_name=en, confidence_score=1.0), warnings=[], statistics=None, is_error=False), DetectLanguageResult(id=3, primary_language=DetectedLanguage(name=Chinese_Traditional, iso6391_name=zh_cht, confidence_score=1.0), warnings=[], statistics=None, is_error=False), DetectLanguageResult(id=4, primary_language=DetectedLanguage(name=Japanese, iso6391_name=ja, confidence_score=1.0), warnings=[], statistics=None, is_error=False), DetectLanguageResult(id=5, primary_language=DetectedLanguage(name=Korean, iso6391_name=ko, confidence_score=1.0), warnings=[], statistics=None, is_error=False)]

輸入幾個句子就會傳回幾個偵測結果, 我們可以用迴圈來列出偵測結果 :

>>> for r in response:    
    print(f'句子 {int(r.id) + 1} 語言: {r.primary_language.name}')   
    
句子 1 語言: Chinese_Simplified
句子 2 語言: English    (錯誤)
句子 3 語言: English
句子 4 語言: Chinese_Traditional
句子 5 語言: Japanese
句子 6 語言: Korean

除了句子 2 偵測錯誤 (應該是 French) 外, 其餘都正確. 錯誤原因可能是句子太短, 偵測模型把它誤認為是英語. 

我們可以用內建函式 enumerate() 來改善上面的輸出結果 :

>>> for id, r in enumerate(response):   
    print(f'"{docs[id]}" : {r.primary_language.name}')    
    
"我爱你" : Chinese_Simplified
"Je t'aime" : English
"I love you" : English
"我愛你" : Chinese_Traditional
"愛してます" : Japanese
"사랑해요" : Korean

函式 enumerate() 會傳回 response 串列元素 id 與元素值組成之 tuple 串列, 這樣便能取得元素之 id, 它的順序是與傳入參數 docs 內的文本是對應的, 因此可用來取得對應之輸入文本. 關於 enumerate() 用法參考 :


在上面測試中我們將金鑰直接以明碼寫在 key 變數中, 這在用 Colab 做教學或分享到 GitHub 時很容易不慎將金鑰洩漏出去, 比較好的做法是將金鑰儲存在系統變數中, 用到時再取出來, 作法參考下面這篇 : 


先用記事本將金鑰記在常數名稱 AZURE_AI_API 內, 例如 : 

AZURE_AI_API=06f8a834tony4bdbbe264504b196606f

然後以 utf-8 格式儲存為 .env 檔. 接著用 pip install 安裝 python-dotenv 套件, 就可以從 dotenv 匯入 load_dotenv() 函式來載入系統變數, 利用 os.load_environ.get('AZURE_AI_API') 來載入金鑰. 

茲將上面測試程式碼寫成如下單一程式檔 : 

# azure_ai_detect_language.py
import os
from dotenv import load_dotenv
from azure.ai.textanalytics import TextAnalyticsClient
from azure.core.credentials import AzureKeyCredential

def authenticate_client(endpoint, key):
    credential=AzureKeyCredential(key)
    client=TextAnalyticsClient(endpoint=endpoint,
                               credential=credential)
    return client

if __name__ == '__main__':
    load_dotenv()
    key=os.environ.get('AZURE_AI_API')
    endpoint='https://tony-test1.cognitiveservices.azure.com/'
    client=authenticate_client(endpoint, key)
    docs=['我爱你',
          "Je t'aime",
          'I love you', '我愛你',
          '愛してます', 
          '사랑해요']
    response=client.detect_language(documents=docs)
    for id, r in enumerate(response):   
        print(f'"{docs[id]}" : {r.primary_language.name}')  

執行結果如下 :

>>> %Run azure_ai_detect_language.py   
"我爱你" : Chinese_Simplified
"Je t'aime" : English       (錯誤)
"I love you" : English
"我愛你" : Chinese_Traditional
"愛してます" : Japanese
"사랑해요" : Korean

原始碼存放於 GitHub :


沒有留言:

張貼留言