Loading Now

Tạo AI Chatbot với AWS Lambda Function Và Amazon EFS

Hôm nay mình sẽ tiếp tục mang đến một chủ đề vừa thú vị, vừa mang tính cách mạng trong ngành công nghiệp phần mềm nói riêng và thế giới nói chung đó là: Xây dựng AI Chatbot Conversation với AWS Lambda Function và Amazon EFS.

AWS Lambda & Amazon EFS là gì?

Cách đây không lâu Amazon công bố tính khả dụng chung của việc hỗ trợ AWS Lambda cho Hệ thống tệp đàn hồi của Amazon (Amazon Elastic File System). Amazon EFS là một hệ thống tệp chia sẻ được quản lý hoàn chỉnh, co giãn linh hoạt và được thiết kế để sử dụng bởi các dịch vụ AWS khác.

Với việc phát hành Amazon EFS cho Lambda, giờ đây chúng ta có thể dễ dàng chia sẻ dữ liệu qua các yêu cầu chức năng. Nó cũng mở ra các khả năng mới, chẳng hạn như xây dựng/nhập các thư viện lớn và các mô hình học máy trực tiếp vào các chức năng Lambda. Hãy để cuốn sách hướng dẫn cách xây dựng một chatbot AI đàm thoại không có máy chủ bằng cách sử dụng chức năng Lambda và EFS.

Trong bài viết này chúng ta sẽ:

  • Tạo một Amazon EFS
  • Triển khai và chạy SageMaker instance và Mount EFS instance.
  • Tải xuống thư viện PyTorch và mô hình được đào tạo trước ConvAI về EFS.
  • Thêm bảng lịch sử hộp thoại trong DynamoDB và điểm cuối Gateway để lưu và truy xuất lịch sử hội thoại
  • Triển khai “chatbot engine” Lambda function và kích hoạt EFS cho nó.

Dưới đây là sơ đồ kiến trúc:

Cách tạo một Amazon EFS

Trong ví dụ này, chúng ta sẽ sử dụng CloudFormation để tạo và truy cập EFS, cấu hình được xác định như sau:

FileSystem:
    Type: AWS::EFS::FileSystem
    Properties:
      PerformanceMode: generalPurpose
      FileSystemTags:
        - Key: Name
          Value: fs-pylibs
  MountTargetA:
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId:
        Ref: FileSystem
      SubnetId: "{{resolve:ssm:/root/defaultVPC/subsetA:1}}"
      SecurityGroups:
        - "{{resolve:ssm:/root/defaultVPC/securityGroup:1}}"
  MountTargetB:
    Type: AWS::EFS::MountTarget
    Properties:
      FileSystemId:
        Ref: FileSystem
      SubnetId: "{{resolve:ssm:/root/defaultVPC/subsetB:1}}"
      SecurityGroups:
        - "{{resolve:ssm:/root/defaultVPC/securityGroup:1}}"
  AccessPointResource:
    Type: "AWS::EFS::AccessPoint"
    DependsOn: FileSystem
    Properties:
      FileSystemId: !Ref FileSystem
      PosixUser:
        Uid: "1000"
        Gid: "1000"
      RootDirectory:
        CreationInfo:
          OwnerGid: "1000"
          OwnerUid: "1000"
          Permissions: "0777"
        Path: "/py-libs

Lưu ý rằng chúng ta sẽ sử dụng chế độ hiệu xuất là EFS General Purpose vì nó có độ trễ thấp hơn so với MAX I/O.

Làm việc với Amazon SageMaker

Chúng ta sẽ gắn EFS trên Amazon SageMaker với một SageMaker Notebook và cài đặt PyTorch và mô hình ConvAI trên EFS.

Lưu ý “notebook” instance của chúng ta phải có quyền truy cập vào cùng một nhóm bảo mật và nằm trong cùng một VPC như hệ thống tệp EFS.

 

Hãy gắn đường dẫn EFS /py-libs vào thư mục /home/ec2-user/SageMaker/libs

:

%%sh

mkdir -p libs

FILE_SYS_ID=fs-xxxxxx

sudo mount -t nfs \
    -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 \
    $FILE_SYS_ID.efs.ap-southeast-2.amazonaws.com:/ \
    libs

cd libs && sudo mkdir -p py-libs

cd .. && sudo umount -l /home/ec2-user/SageMaker/libs

sudo mount -t nfs \
    -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 \
    $FILE_SYS_ID.efs.ap-southeast-2.amazonaws.com:/py-libs \
    libs

Sau đó cài đặt (thao tác trên linux) PyTorch và simpletransformers vào thư mục lib/py-libs

!sudo pip --no-cache-dir install torch -t libs/py-libs

!sudo pip --no-cache-dir install torchvision -t libs/py-libs

!sudo pip --no-cache-dir install simpletransformers -t libs/py-libs



Khi chúng ta đã cài đặt tất cả các gói cần thiết, tải xuống pre-trained model được cung cấp bởi Hugging Face, giải nén nó trong thư mục 

convai-model trên EFS.

!sudo wget https://s3.amazonaws.com/models.huggingface.co/transfer-learning-chatbot/gpt_personachat_cache.tar.gz
!sudo tar -xvf gpt_personachat_cache.tar.gz -C libs/convai-model
!sudo chmod -R g+rw libs/convai-model

Bây giờ chúng tôi đã sẵn sàng để nói chuyện với mô hình được đào tạo trước, chỉ cần gọi model.interact()

Mô hình được đào tạo trước được cung cấp bởi Hugging Face, có thể chạy tốt ngoài luồng và cần ít tinh chỉnh hơn khi chúng ta tạo chatbot.

Chúng ta có thể thấy rằng các gói và mô hình python được tiêu thụ chính xác từ EFS và chúng ta có thể bắt đầu cuộc trò chuyện với mô hình được đào tạo trước. Mô hình đào tạo trước là những gì chúng ta đã “dạy” cho máy hiểu các trường hợp có thể xảy ra từ đó đưa ra phản hồi chính xác hơn.

Tạo bảng AWS DynamoDB

Tạo bảng DialogHistory để lưu trữ lịch sử hộp thoại với ít nhất là cách nói cuối cùng từ người dùng. Chúng ta có thể sử dụng các mẫu CloudFormation có sẵn để cấu hình bảng DynamoDB. Xin lưu ý rằng Chúng ta phải tạo điểm cuối VPC cho DynamoDB mặc dù chức năng Lambda đang chạy bên trong “public subnet” của VPC, tìm hiểu thêm tại đây.

Cấu hình AWS Lambda để dùng EFS

Chúng ta sẽ sử dụng AWS SAM để tạo Lambda functions và “mount” EFS tạo điểm truy cập vào Lambda function.

Đầu tiên khởi tạo tài nguyên Lambda function, sau đó cài đặt EFS cho Lambda. Hãy chắc chắn rằng Lambda và EFS cùng nằm trong cùng một VPC:

HelloFunction:
    Type: AWS::Serverless::Function
    DependsOn:
      - LibAccessPointResource
    Properties:
      Environment:
        Variables:
          CHAT_HISTORY_TABLE: !Ref TableName
      Role: !GetAtt LambdaRole.Arn
      CodeUri: src/
      Handler: api.lambda_handler
      Runtime: python3.6
      FileSystemConfigs:
        - Arn: !GetAtt LibAccessPointResource.Arn
          LocalMountPath: "/mnt/libs"
      VpcConfig:
        SecurityGroupIds:
          - "{{resolve:ssm:/root/defaultVPC/securityGroup:1}}"
        SubnetIds:
          - "{{resolve:ssm:/root/defaultVPC/subsetA:1}}"
          - "{{resolve:ssm:/root/defaultVPC/subsetB:1}}"
  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "efsAPILambdaRole"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AWSLambdaExecute"
        - "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
        - "arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess"
      Policies:
        - PolicyName: "efsAPIRoleDBAccess"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "dynamodb:PutItem"
                  - "dynamodb:GetItem"
                  - "dynamodb:UpdateItem"
                  - "dynamodb:DeleteItem"
                  - "dynamodb:Query"
                  - "dynamodb:Scan"
                Resource:
                  - !GetAtt ChatHistory.Arn
                  - Fn::Join:
                      - "/"
                      - - !GetAtt ChatHistory.Arn
                        - "*"
              - Effect: Allow
                Action:
                  - "ssm:GetParameter*"
                Resource:
                  - !Sub "arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/root/defaultVPC*"

Thêm vào conversation egnine: AWS Lambda

Trong phần này chúng ta sẽ tạo một Lambda function để người dùng (user) có thể giao tiếp với conversation AI model (chatbot).

Chúng ta sẽ lưu đoạn code dưới đây trong src/api.py

import json
import logging
import sys
import boto3
import random
import os
sys.path.insert(1, '/mnt/libs/py-libs')
import torch
import torch.nn.functional as F
from simpletransformers.conv_ai.conv_ai_utils import get_dataset
from simpletransformers.conv_ai import ConvAIModel


def get_chat_history(userid):
    response = dynamodb.get_item(TableName=TABLE_NAME, Key={
        'userid': {
            'S': userid
        }})

    if 'Item' in response:
        return json.loads(response["Item"]["history"]["S"])
    return {"history": []}


def save_chat_history(userid, history):
    return dynamodb.put_item(TableName=TABLE_NAME, Item={'userid': {'S': userid}, 'history': {'S': history}})


def lambda_handler(event, context):
    try:
        userid = event['userid']
        message = event['message']
        history = get_chat_history(userid)
        history = history["history"]
        response_msg = interact(message, convAimodel,
                                character, userid, history)

        return {
            'message': json.dumps(response_msg)
        }
    except Exception as ex:
        logging.exception(ex)

Chú ý: Thư viện simpletransformers cho phép chúng ta tương tác với các mô hình trên local thông qua hàm input(). Để xây dựng chatbot engine cho chúng ta, mình sẽ ghi đè lên mặc định của các phương thức tương tác và sample_sequenc trong conv_ai :

def sample_sequence(aiCls, personality, history, tokenizer, model, args, current_output=None):
    special_tokens_ids = tokenizer.convert_tokens_to_ids(SPECIAL_TOKENS)
    if current_output is None:
        current_output = []

    for i in range(args["max_length"]):
        instance = aiCls.build_input_from_segments(
            personality, history, current_output, tokenizer, with_eos=False)

        input_ids = torch.tensor(
            instance["input_ids"], device=aiCls.device).unsqueeze(0)
        token_type_ids = torch.tensor(
            instance["token_type_ids"], device=aiCls.device).unsqueeze(0)

        logits = model(input_ids, token_type_ids=token_type_ids)
        if isinstance(logits, tuple):  # for gpt2 and maybe others
            logits = logits[0]
        logits = logits[0, -1, :] / args["temperature"]
        logits = aiCls.top_filtering(
            logits, top_k=args["top_k"], top_p=args["top_p"])
        probs = F.softmax(logits, dim=-1)

        prev = torch.topk(probs, 1)[
            1] if args["no_sample"] else torch.multinomial(probs, 1)
        if i < args["min_length"] and prev.item() in special_tokens_ids:
            while prev.item() in special_tokens_ids:
                if probs.max().item() == 1:
                    logging.warn(
                        "Warning: model generating special token with probability 1.")
                    break  # avoid infinitely looping over special token
                prev = torch.multinomial(probs, num_samples=1)

        if prev.item() in special_tokens_ids:
            break
        current_output.append(prev.item())

    return current_output


def interact(raw_text, model, personality, userid, history):
    args = model.args
    tokenizer = model.tokenizer
    process_count = model.args["process_count"]

    model._move_model_to_device()

    if not personality:
        dataset = get_dataset(
            tokenizer,
            None,
            args["cache_dir"],
            process_count=process_count,
            proxies=model.__dict__.get("proxies", None),
            interact=True,
        )
        personalities = [dialog["personality"]
                         for dataset in dataset.values() for dialog in dataset]
        personality = random.choice(personalities)
    else:
        personality = [tokenizer.encode(s.lower()) for s in personality]

    history.append(tokenizer.encode(raw_text))
    with torch.no_grad():
        out_ids = sample_sequence(
            model, personality, history, tokenizer, model.model, args)
    history.append(out_ids)
    history = history[-(2 * args["max_history"] + 1):]
    out_text = tokenizer.decode(out_ids, skip_special_tokens=True)
    save_chat_history(userid, json.dumps({"history": history}))
    return out_text

Triển khai dịch vụ của chatbot

Vậy là chúng ta đã gần hoàn tất công việc, tiếp theo là triển khai chatbot bằng các chạy đoạn code dưới đây:

Trong outout bên trên chúng ta có thể thấy chatbot đã được triển khai thành công.

Bây giờ là lúc để kiểm tra thành quả chúng ta, vào danh sách CloudFormation Resources trong AWS Management Console để tìm tên của Lambda function sau đó gọi Lambda function bằng câu lệnh sau:

$aws lambda invoke --function-name "chat-efs-api-HelloFunction-KQSNKF5K0IY8" out --log-type Tail \--query 'LogResult' --output text | base64 -d

Output hiển thị ra sẽ giống như dưới đây:

Dưới đây là một đoạn hội thoại ví dụ:

Như các bạn thấy, chatbot đã hoạt động. Tuy nhiên có một vấn đề nhỏ về thời gian phản hồi của chatbot, nó khá chậm và mất tầm 30s cho Cold starts hay còn gọi là thời gian ngưng trệ, trong trường hợp này để ngăn chặn Cold starts đương nhiên nhiên phải giữ cho chatbot của chúng ta luôn “ấm” bằng cách sử dụng Provisioned Concurrency

Phần này các bạn sẽ tự tìm hiểu thêm qua link tài liệu mình gắn bên trên. (phần source hoàn chình mình để ở cuối bài viết.)

Kết luận

Vậy là chúng ta đã cùng tìm hiểu về cách xây dựng một chatbot với AWS Lambda FunctionAmazon EFS . Bài viết sử dụng khá nhiều thuật ngữ chuyên môn và nhiều thư viện, mình mong rằng các bạn sẽ tự tìm hiểu thêm và chia sẻ những kiến thức hay ho mà bản thân tìm hiểu được. Kết thúc bài thứ 2 trong “series – Sành điệu cùng Python” cảm ơn các bạn đã ủng hộ, trong quá trình viết bài còn nhiều sai sót mong các bạn cùng góp ý để hoàn thiện hơn. Cảm ơn và hẹn gặp lại các bạn trong các bài viết tiếp theo.

Thanks for reading!

Dung Tran – Push Manager / Software Engineer

Nguồn: Tham khảo

Link source code

Post Comment

Contact