原文:towardsdatascience.com/create-an-a-i-driven-product-with-computer-vision-and-chatgpt-070a34ab9877

TL;DR

在本文中,您将学习如何

  • 训练自定义和预训练的计算机视觉模型

  • 为医疗用例构建一个 LLM 语言系统

  • 漫步应用的使用

  • 任何数据集都可以创建一个 AI 驱动产品


这篇文章是为谁而写的?

本文旨在面向以下受众:

  1. 学生是新数据科学领域的入门者,他们希望学习如何在数据科学训练营的范围内结合技术创造有用的东西。

  2. 数据科学家希望学习如何将GPT 模型集成到他们的项目中。

  3. 雇主正在寻找招聘数据科学家或机器学习工程师。您可以通过**LinkedIn**联系我。


代码

本项目的所有代码都可以在我的 GitHub 个人资料中找到。

my_portfolio/melanoma_cancer at main · Alexander-Barriga/my_portfolio

README文件中还有一个简短的演示视频,其中包含了上面的屏幕截图。


! 声明 !

在我们开始文章的主要内容之前,请注意,这个用于黑色素瘤分类的原型仅用于教育目的!现实生活中黑色素瘤的诊断需要经过培训的医疗专家的评估和皮肤样本的实验室检测。

您可以在 Dana-Farber 癌症研究所网站上了解更多关于严格的医疗流程的信息**这里**。

此外,本文中提供的分类解决方案存在局限性,包括但不限于:

  1. 机器学习模型是非常脆弱的算法,它们需要在使用数据进行训练以及寻求可靠预测时进行大量的关注和准备。

  2. 上传一个痣的图片可能会让模型误以为它看到了恶性的黑色素瘤。你甚至可以给模型提供非皮肤图像,它仍然会给出预测。

作为机器学习从业者,我们必须记住我们工具的限制。这一点在我们的工作影响人类和动物的健康与生活时尤为重要。


动机

**我在General AssemblyBloomTech**总共教了 100 多名数据科学学生。课程结束时,学生将完成一个综合项目,展示他们所学的所有技能。

在 BloomTech,我教授了深度学习 101 课程,其中包括深度学习的各种应用,包括自然语言处理(NLP)和计算机视觉(CV)。

在这篇文章中,我将向你展示如何创建一个由人工智能驱动的软件医疗产品的原型。我们将使用一个 真实世界黑色素瘤图像数据集 来训练分类模型,以预测图像是否显示良性或恶性黑色素瘤的迹象,这是一种皮肤癌。

然后,我将向你展示如何创建一个定制的 LLM 语言系统,该系统结合了检索增强生成(RAG),用于黑色素瘤信息的具体用例。

最后,我将向你展示如何使用 Dash 将你的 CV 和 GPT 模型插入仪表盘应用程序的后端。

我相信健康是我们生活中可以做的最重要的财富投资。这个项目与我的个人价值观相符,让我对其成功充满信心。

数据科学学生

  • 我希望你能注意到,这大约是你在数据科学训练营的毕业项目中可以期待的复杂度(可能略高)。

在职数据科学家

  • 我希望你能注意到,相对而言,原型化一个新想法以向产品经理和其他利益相关者展示你的提案是多么直接。

计算机视觉

在本节中,我们将介绍数据和我们的计算机视觉工具,以及讨论训练后的模型评估。

深度学习库

在众多 Python 深度学习库中选择时,有几个流行的选项:

Keras 是一个流行的库,数据科学学生和在职专业人士都因其易于使用的 API 而喜爱,它使你能够快速迭代实验并展示一个概念验证(POC)。正因为如此,我们将使用 Keras 来完成这个项目。

我们将要使用的数据集被称为 10000 张图像的黑色素瘤皮肤癌数据集,可以在 Kaggle 上找到。这是一个包含 1 万张良性或恶性黑色素瘤图像的二分类数据集。良性类和恶性类数量接近平衡,并以 300×300 分辨率提供。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e3f0998da0757fa07621f17d3b76f4a4.png

病理性黑色素瘤图像样本(图片由作者提供)。

batch_size = 32
train_gen = tf.keras.utils.image_dataset_from_directory(
            TRAIN_DIR,
            labels='inferred',
            label_mode='int',
            class_names=None,
            color_mode='rgb',
            batch_size=batch_size,
            image_size=(300, 300),
            shuffle=True,
            seed=SEED,
            interpolation='bilinear',
            crop_to_aspect_ratio=True,
        )

test_gen = tf.keras.utils.image_dataset_from_directory(
            TEST_DIR,
            labels='inferred',
            label_mode='int',
            class_names=None,
            color_mode='rgb',
            batch_size=batch_size,
            image_size=(300, 300),
            shuffle=True,
            seed=SEED,
            interpolation='bilinear',
            crop_to_aspect_ratio=True,
        )

在拥有 1 万张图像的数据集时,我们需要考虑内存管理。幸运的是,Keras 提供了实用函数,我们可以提供存储图像的目录位置,它们将通过生成器分批加载到内存中。

模型

我们将训练两种类型的模型:从头开始构建的简单 AlexNet 模型和几个在 Keras 预训练 CNN 模型中找到的大型预训练模型。

def create_alexnet_model(input_shape, batch_size):
    model = Sequential([
        Input(shape=input_shape, batch_size=batch_size),
        Rescaling(1./255),
        Conv2D(96, (11, 11), strides=(4, 4), activation='relu'),
        MaxPooling2D((3, 3), strides=(2, 2)),
        Conv2D(256, (5, 5), padding='same', activation='relu'),
        MaxPooling2D((3, 3), strides=(2, 2)),
        Conv2D(384, (3, 3), padding='same', activation='relu'),
        Conv2D(384, (3, 3), padding='same', activation='relu'),
        Conv2D(256, (3, 3), padding='same', activation='relu'),
        MaxPooling2D((3, 3), strides=(2, 2)),
        Flatten(),
        Dense(4096, activation='relu'),
        Dropout(0.5),
        Dense(2096, activation='relu'),
        Dropout(0.5),
        Dense(1, activation='sigmoid') # binary classification
    ])
    return model

我们将使用以下预训练模型

  • densenet 121

  • InceptionResNetV2

  • Xception

def transfer_learning_model(input_shape, batch_size):

    # Load pre-trained model without including top layers (fully connected layers)
    base_model = Xception(weights='imagenet', include_top=False, input_shape=input_shape)

    # Don't freeze the base model
    base_model.trainable = False

    # Add custom top layers for binary classification
    model = tf.keras.Sequential([
        #Input(shape=input_shape, batch_size=batch_size),
        Resizing(299, 299, crop_to_aspect_ratio=True),
        Rescaling(1./255),
        base_model,
        keras.layers.GlobalAveragePooling2D(),

        keras.layers.Dense(256, activation='relu'),  # Add additional Dense layers
        keras.layers.Dropout(0.5),  # dropout layer for regularization

        keras.layers.Dense(128, activation='relu'),  # Add another Dense layer
        keras.layers.Dropout(0.5),  # dropout layer for regularization

        keras.layers.Dense(1, activation='sigmoid')  # Final classification layer
    ])

    return model

注意,预训练的 CNN 模型将各自有不同的输入要求。Xception 要求输入图像的分辨率为 299×299,因此包含了一个调整大小层。

我们可以选择包含原始顶层(即模型的完全连接前馈部分,包括分类层)以及我们是否想要训练基础模型(即卷积层)。

当你自由地实验这些选项时,防止基础模型重新训练将导致训练时间更快。此外,这些预训练模型大多数都是在名为imagenet的大规模数据集上训练的,该数据集包含数千个独特的图像,因此在分类层中有数千个节点。我们的用例只需要为我们的二分类任务 1 个节点。这就是为什么我们需要创建自己的顶层以确保模型只预测 2 个类别之一:良性或恶性

CPU vs GPUs vs TPUs

在训练深度学习模型时,通常使用 GPU 或 TPU 代替 CPU。这在计算机视觉的背景下尤其如此;较大的图像通常需要训练更多的权重,因此训练时间更长。

GPU 最初是为图形处理设计的,但非常适合执行深度学习算法中发生的矩阵乘法。从而显著提高训练速度。

另一方面,TPU 是由谷歌专门针对深度学习创建的。你可以在这里了解更多关于它们的信息。谷歌将 TPU 描述为

一种专门用于神经网络工作负载的矩阵处理器。TPU 不能运行文字处理器、控制火箭引擎或执行银行交易,但它们可以以极快的速度处理神经网络中的大量乘法和加法,同时消耗更少的电力,并在更小的物理尺寸内。

访问 GPU 和 TPU

在工业界,AWS 通常被用来启动一个训练环境,该环境包括访问几乎所有可能需要的计算资源来训练大型模型及其大型数据集。

然而,当训练深度学习模型时,快速、简便(且免费)地利用 GPU 和 TPU 加速的一种方法是使用**Google Colab**。尽管免费版本对 GPU 和 TPU 以及内存访问有限制。但它还有一个低成本的 Pro 版本,可以为你提供更多的计算资源,例如不间断地访问 GPU 和 TPU。

本项目利用了 Google Colab 的 Pro 版本。只需上传您的笔记本,选择您想要的处理器,然后点击‘运行’。

模型评估

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f5703c0788ce77347ea2e2d72ec177c7.png

每个模型的 ROC 和 AUC 图(作者提供)

在所有四个模型在 TPU 上训练完成后,我们现在需要评估它们的性能。上面的 ROC 曲线显示了每个模型在测试集上真实和假阳性预测之间的权衡。

右下角的图例显示了每个模型的 AUC 准确度分数,这是每个曲线下面积的总和。你可以从曲线和 AUC 分数中看到,所有模型的性能大致相同,而迁移模型的性能略优于 AlexNet。记住,我们没有费心训练基础模型来获得这些出色的结果。作为一个原型,这些结果是非常好的。然而,在将此模型投入生产之前,我们希望探索重新训练基线,看看我们能否将模型的性能提升到接近 100%,毕竞这是一个医疗救命的应用。

绘制 ROC 曲线的主要好处是,我们可以看到对于哪个模型我们应该扫描最佳概率阈值值,以便最大化真正阳性和最小化假阳性。

为了进一步分析,我们将扫描最佳阈值值,并根据混淆矩阵的结果选择一个模型。这并不是仅仅通过查看图表就能做到的。你必须编写一个选择算法来程序化地为你完成这项工作。

def get_best_threshold(y_true, y_pred_prob):
    # Assuming you have computed the true labels and predicted probabilities
    fpr, tpr, thresholds = roc_curve(y_true, y_pred_prob)

    # Compute the False Negative Rate (FNR) and False Positive Rate (FPR) for each threshold
    fnr = 1 - tpr
    fpr_max = np.maximum(fpr, 0.1)  # Set a maximum threshold for FPR

    # Find the index of the threshold that minimizes the difference between FNR and FPR
    difference = fnr - fpr_max
    best_threshold_index = np.argmin(np.abs(difference))

    # Get the threshold value corresponding to the index
    best_threshold = thresholds[best_threshold_index]

    print("Best threshold:", best_threshold)
    print("TPR:", tpr[best_threshold_index])
    print("FPR:", fpr[best_threshold_index])

    return best_threshold, tpr[best_threshold_index], fpr[best_threshold_index]

为了简单起见,我们将选择迁移模型预测概率中的一个来进行扫描,因为所有迁移模型的性能大致相同。通过传递 Xception 的预测概率,我们得到以下结果:

最佳阈值: 0.3677

TPR: 0.897

FPR: 0.068

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/39bf9fef8a2f36b5ffe69934bb71dd33.png

所有 4 个模型的混淆矩阵(作者提供)

通过选择 Xception 的最佳概率阈值值 0.367,并对所有 4 个模型的测试集图像进行重新分类,我们得到了上面的混淆矩阵。

我们可以看到,它们的性能非常相似。对于我们的用例,我们优先考虑最小化假阴性:我们不希望错误地通知一个人,他们有恶性黑色素瘤,而实际上它是良性的。因此,我们将继续使用 DenseNet 模型,该模型具有最低的假阴性数量。

DenseNet 具有最少的假阴性(类型 2 错误)数量,但所有模型中假阳性(类型 1 错误)的数量最高(与 AlexNet 并列)。每种类型错误的数量可以根据项目的需求进行调整。

假阴性意味着一个患者会被告知他们没有癌症,但实际上他们有癌症;而假阳性则意味着一个患者会被告知他们有癌症,但实际上他们没有。主观上,假阴性是一种更严重的错误类型,因为未经治疗的黑色素瘤可能是致命的,而假阳性可能导致对良性皮肤斑块的恐慌。

使用新的阈值,在测试集上计算了 DenseNet 的以下指标。

准确度:0.9176

精确度:0.9403

召回率:0.8909

F1 分数:0.9149

我们选择的阈值导致了上述二元分类指标值。所有值都接近或超过 90%。这意味着 10 个预测中有 9 个将是正确的(准确度)。10 个阳性预测中有 9 个将是正确阳性的(精确度)。在所有实际阳性病例中,模型正确识别的实际阳性病例的比例也是 90%(召回率);这意味着 10%的黑色素瘤患者将被归类为没有黑色素瘤。最后,F1 分数是精确度和召回率的平衡平均值,它告诉我们模型有 90%的预测是正确的,并且精确度和召回率之间没有不平衡。

太棒了!现在我们已经验证了训练模型的性能并选择了 DenseNet 作为我们的应用程序,我们可以继续到我们医疗产品的聊天机器人部分。


定制 OpenAI 的 ChatGPT LLM

class chatbot():
    def __init__(self):
        self.llm = ChatOpenAI() 
        self.n_docs_retrieve = 10
        self.embeddings = OpenAIEmbeddings()

        self.__load_docs()
        self.__set_prompt()

    def __load_docs(self):
        docs = []
        for doc in os.listdir(os.getenv("PDF_FOLDER_PATH")):
            if doc.endswith('.pdf'):
                pdf_path = os.path.join(os.getenv("PDF_FOLDER_PATH"), doc)
                loader = PyPDFLoader(pdf_path)
                docs.extend(loader.load_and_split())

        self.faiss_index = FAISS.from_documents(docs, self.embeddings) 

    def __set_prompt(self):
        self.prompt = ChatPromptTemplate.from_template("""Your role is to provide information regarding melanoma. 
        Attempt to answer the following question based only on the provided context:

            <context>
            {context}
            </context>

        If the context doesn't provide an answer, provide an answer from your own knowledge.
        Question: {input}""")

    def get_retrieval_augmented_answer(self, query):
        # find docs sim to prompt
        docs = self.faiss_index.similarity_search(query, k=self.n_docs_retrieve) # find docs sim to prompt

        text_splitter = RecursiveCharacterTextSplitter()
        documents = text_splitter.split_documents(docs)
        vector = FAISS.from_documents(documents, self.embeddings) # we have this data indexed in a vectorstore

        document_chain = create_stuff_documents_chain(self.llm, self.prompt)

        # create retrieval 
        retriever = vector.as_retriever() 
        retrieval_chain = create_retrieval_chain(retriever, document_chain)

        # get response for prompt
        self.response = retrieval_chain.invoke({"input": query})
        return self.response["answer"]

我们医疗产品的第二个 AI 后端插件是医疗助手聊天机器人。在上面的代码中,我们可以看到我们如何将 OpenAI 的 GPT 模型集成到检索增强生成(RAG)语言系统中。在这个例子中,我们使用的框架是**语言模型系统,即Langchain**。

RAG 意味着我们在回答用户问题时,为 LLM 提供了参考文档。 这是一个高度期望的特性,原因有两个:

  1. 它最小化了 LLM 产生幻觉的可能性。

  2. 它为 LLM 提供了它在训练期间可能没有见过的数据。

虽然黑色素瘤是一种相对了解的癌症类型,但我们不希望 LLM 对高度敏感的医疗问题提供错误答案。让我们更仔细地看看我们的聊天机器人的部分。

加载文档

def __load_docs(self):
        docs = []
        for doc in os.listdir(os.getenv("PDF_FOLDER_PATH")):
            if doc.endswith('.pdf'):
                pdf_path = os.path.join(os.getenv("PDF_FOLDER_PATH"), doc)
                loader = PyPDFLoader(pdf_path)
                docs.extend(loader.load_and_split())

        self.faiss_index = FAISS.from_documents(docs, self.embeddings)

这是我们加载关于黑色素瘤的精选文档的地方,将文档分成更小的部分,创建向量嵌入,并将它们存储在**向量存储库**中。我们想要将这些 pdf 存储到向量嵌入中的原因是为了在搜索相关信息回答用户问题时提供更快的查找时间。它提供了一种存储非结构化数据的方法。

我们使用的向量存储称为FAISS,由**Meta创建。我们加载了两份关于黑色素瘤的 pdf 文档。它们是公开可用的文档。如果你想查看,可以查看文档 1文档 2**。

设置提示

def __set_prompt(self):
        self.prompt = ChatPromptTemplate.from_template("""Your role is to provide information regarding melanoma. 
        Attempt to answer the following question based only on the provided context:

            <context>
            {context}
            </context>

        If the context doesn't provide an answer, provide an answer from your own knowledge.
        Question: {input}""")

在这里,我们设置提示,以便能够使用我们提供的任何上下文来回答问题。如果我们提出像“放射疗法如何帮助治疗恶性黑色素瘤?”这样的问题,语言系统将查找相关信息并将其嵌入到提示中,对其进行释义以回答问题。

在上面的提示中,我们告诉 LLM 在上下文不足的情况下使用它在训练期间获得的关于黑色素瘤的信息来回答问题。我们还可以告诉 LLM 不要回答没有提供上下文的问题,作为防止提供可能不准确信息的预防措施。

检索增强生成

def get_retrieval_augmented_answer(self, query):
    # find docs sim to prompt
    docs = self.faiss_index.similarity_search(query, k=self.n_docs_retrieve)
    text_splitter = RecursiveCharacterTextSplitter()
    documents = text_splitter.split_documents(docs)

    # we have the pdf data indexed in a vectorstore as embeddings
    vector = FAISS.from_documents(documents, self.embeddings) 

    document_chain = create_stuff_documents_chain(self.llm, self.prompt)

    # create retrieval 
    retriever = vector.as_retriever() 
    retrieval_chain = create_retrieval_chain(retriever, document_chain)

    # get response for prompt
    self.response = retrieval_chain.invoke({"input": query})
    return self.response["answer"]

这是实际检索的主要方法。首先,查询被传递到向量存储中,以在用户的查询(即问题)和现有上下文(即黑色素瘤文档)之间执行相似度搜索。然后,它将提示、llm 和检索到的文档放入一个链中(即操作顺序)。最后,将生成的答案返回给用户。


仪表板

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/aa500eab419321b2a69d8cbc4d24a114.png

作者提供的图像

CV 和 LLM 被连接到使用**Dash框架编写的应用中,用于构建简单的 Web 应用。我们不会在这里分解仪表板代码,以防止文章变得过长。相反,我们将通过如何使用该应用进行说明。回想一下,你可以在我的Github 仓库**上查看演示视频(以及所有代码)。

  1. 通过点击左上角的“上传文件”按钮上传任何大小的疑似黑色素瘤图像。

  2. 在右上角查看 DeepNet 对图像包含恶性或良性黑色素瘤概率的预测。

  3. 在左下角查看良性黑色素瘤和恶性黑色素瘤的参考图像。

  4. 将你关于黑色素瘤的任何问题写入右下角的 AI 聊天机器人,并查看回复。

就这些!这就是我们可以将几种技术结合起来创建一个由人工智能驱动的软件应用的方法。在这个例子中,我决定讨论癌症,因为我对健康的个人兴趣。


启动项目的技巧

无论你是考虑毕业设计的数据科学学生,还是考虑开始新个人项目的数据科学家,我都想给你一些建议,让你使头脑风暴过程变得更容易。

  1. 思考你的价值观

你在生活中看重什么?是健身、旅行、约会、清洁能源、电子游戏、电影,甚至可能是哲学和文学?这是一个好的开始,因为你是你自己的价值观。所以如果你能在工作中看到自己,你将找到你需要的动力、意义和重要性,以帮助你清除心理空间中的杂乱和不确定性,并看到潜在项目中的价值。

  1. 解决其他人愿意为之付费的问题

现在你想要找到你重视的和他人重视的东西之间的交集。在这篇文章中,我特别选择了健康和癌症,但另一个例子可以是旅行。很多人喜欢旅行。如果你能创建一个使旅行后勤保障更容易、行程创建更容易等的应用程序,这将给很多人带来价值。

  1. 技术

现在你已经定义了一个要解决的问题和一个市场,考虑一下哪些技术最适合解决这个问题。在旅行示例中,我认为 LLMs(大型语言模型)在翻译、行程创建等方面非常有用。


结论

我们已经看到如何使用开源数据和科技来创建一个用于黑色素瘤皮肤癌诊断和教育的医疗应用原型。我们使用了计算机视觉来构建图像分类器。我们使用了 OpenAI 的 GPT 模型来创建一个检索增强生成语言系统。然后我们将这两个 AI 模型连接到了由 Dash 创建的仪表板中。

如果你已经读到这儿并且喜欢这篇文章,我希望你能为它鼓掌并分享给任何你认为会从中受益的人。这将对我帮助更多的人非常有帮助。


关于作者

亚历山大·巴里加是一位数据科学家,他在清洁能源、insurTech、edTech 和 NASA 等多个行业教授和应用数据科学。

他目前正在寻找数据科学或机器学习工程的角色。请在**LinkedIn**上联系他。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/ef800e402657fdcedc3a69a8ee181b89.png

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐