2024年1月

Kernel Memory 中使用 PaddleSharp OCR

Kernel Memory 中进行文档处理的时候可以上传图片作为文档,这时候就需要使用到 OCR 技术来识别图片中的文字。

官方默认的库中,提供了 Azure Document Intelligence 的扩展服务,可以通过 Azure 的服务来进行 OCR。

方法也非常简单,只需要在构建 Kernel Memory 的时候,调用
WithAzureAIDocIntel
方法,提供相应的参数即可。

var _ = new KernelMemoryBuilder(appBuilder.Services)
    //...
    .WithAzureAIDocIntel(new AzureAIDocIntelConfig()) // <- register azure document intelligence
    .Build();

如果没有Azure 服务的话,也可以使用自定义的 OCR 服务,例如 PaddleSharp OCR。

实现自定义的 OCR 服务,需要实现
IOcrEngine
接口,该接口的定义相对比较简单,其中只有一个
ExtractTextFromImageAsync
方法。


public interface IOcrEngine
{
    Task<string> ExtractTextFromImageAsync(Stream imageContent, CancellationToken cancellationToken = default(CancellationToken));
}

使用 PaddleSharp 我们需要安装以下 Nuget 包:

Sdcb.PaddleInference
Sdcb.PaddleOCR
Sdcb.PaddleInference.runtime.win64.mkl
OpenCvSharp4.runtime.win
Sdcb.PaddleOCR.Models.Local

然后实现
IOcrEngine
接口:


    public class PaddleSharpOcrEngine(FullOcrModel model) : IOcrEngine
    {
        private readonly FullOcrModel _model = model;

        public async Task<string> ExtractTextFromImageAsync(Stream imageContent, CancellationToken cancellationToken = default)
        {
            using var all = new PaddleOcrAll(_model, PaddleDevice.Mkldnn());
            using var memoryStream = new MemoryStream();
            await imageContent.CopyToAsync(memoryStream);
            using Mat src = Cv2.ImDecode(memoryStream.ToArray(), ImreadModes.Color);
            PaddleOcrResult result = all.Run(src);
            return result.Text;
        }
    }

在构建 Kernel Memory 的时候,注册自定义的 OCR 服务:

var model = LocalFullModels.EnglishV3;
var memory = new KernelMemoryBuilder(appBuilder.Services)
    //...
    .AddSingleton<IOcrEngine>(new PaddleSharpOcrEngine(model))// <- register paddle ocr
    .Build();

注册完成之后,后续处理图片文件的过程中,就会自动调用 PaddleSharp OCR 服务了。

await memory.ImportDocumentAsync("./kernel_memory_readme.png");

var question = "What's Kernel Memory?";

var answer = await memory.AskAsync(question);

Console.WriteLine($"Q: {question}");
Console.WriteLine($"A: {answer.Result}");

Alt text

以上代码实现已经上传至
GitHub
.

参考

大家好,我是
闲者
,因为今年的目标是做个自己的产品,但是却不知道要做什么产品!很纠结。原文发表在我的博客:
闲着博客-程序员创业该做什么产品?

考虑到孙子兵法提过
先胜而战”
,既然不知道要做什么产品,那就看看做什么产品会失败!顺便吐槽下……

关于程序员如何创业,做产品,网上搜一下就是一堆。所以简单的看下哪些产品为什么会失败?哪些产品又为什么会成功?总结下前辈的经验,顺便提醒自己后面做产品的时候如何规避。

首先看下其他自由开发者一般都做哪些产品……

在查看多位自由开发者公开的产品后,以下类别最多:
闹钟app、计算器、记事本、背景设置软件、markdown在线笔记、markdown在线简历、财务记账APP等。

其实开始也不是太懂,为什么基本都是做这些类型的,不过想了想,这不就是把自己的需求转换成产品了么……大概率是在找不到真实需求的情况下,根据自身的想法做的。

那么仔细分析下为什么会出现这种情况,我总结了以下几个缺陷。如果大家有什么不同意见可以评论留言探讨。

一. 失败的共性

缺陷一:带入了职业思维和习惯

基本都是工具类,并且某些产品带入了程序员的思维和习惯,并未考虑到真实用户的使用思维和习惯,或者说,在开发的时候就为明确产品的定位,仅仅是可能会喜欢这一简单的想法。

比如上述中的markdown笔记,markdown在线简历等,不可否认,markdown语法非常简洁,但试想一下,对于普通用户来说,我写个东西,还要背个新语法,看文档,这也太坑了!为什么不用富文本编辑呢?这种产品就是典型的程序员思维产品。这样开发出来的产品或许只有同为程序员的使用者才会感兴趣。

记账app也是非常多的,但有个问题,开发者会使用自己开发的记账app吗?如果使用又会坚持几天?我想没有几个人使用和坚持吧,另外现在一般都用微信和支付宝,这些本身自带账单功能,每到月底拉下账单就好了,也省去了每次花钱还得记账的操作。

还有就是,这些产品同质化太严重了,赛道太卷了,我相信肯定有做的好的产品被使用,但付费的又有多少呢?为爱发电的另算。

一个好的产品将自己的职业思维和习惯带进去,除非,这个产品是为了这个职业而开发的。

当然这点上可能和独立开发者一般都是单打独斗有关系,正常的软件开发,需要经过很多流程,而个人开发者很难做到这些方方面面。

缺陷二:没有真实的需求

从上面这些产品可以看出来,需求都是非常单一的。基本可以称为面向自己开发的产品,因为自己才知道需求在哪里。所以从这方面来看,即便这些产品有很多缺陷,但至少开发者是针对需求开发。

当然,从自己的需求出发,事前做调研,已经是较务实的做法了。以我为例,程序员在思维上比较理性,大多数涉及询问的时候,可不会留面子说“不错”,大多数都是说“不需要”,“别人都能做,你有什么优势”。

总的来说:
这些产品一直在解决“痒点”,而不是真正的“痛点”,本质上其实完全没有解决任何问题。

例如你觉得闹钟app不好,重新造个闹钟app,那叫解决“痒点”,就像别人走 10 步可以到的地方,你让别人省了 2 步,你觉得很嗨,但是对于别人来说,无关痛痒。就是那种,有你,和没有你,根本区别不大。

至于真实的需求,那些能被大多数人需要的东西,肯定有大公司做了,而垂直领域需要的东西,需要跟这个领域有接触,除非之前就在这个领域工作,否则很难凭空想出来。所以这也就不难理解产品同质化严重了。

在我的想法里,一个好的产品出现,应该是针对需求中某个真正的痛点的,而不是伪需求。

缺陷三:无法跳出的圈子

这个缺陷不能称为产品的缺陷,或者换个说法,正是因为这个缺陷,导致大多数程序员无法做出成功的产品。

程序员一般都是理性思维多些,外加工作比较繁忙,整日与电脑打交道,所以交际圈比较窄。这就制约了程序员无法跳出自己的圈子,发现真正的真实需求。

因为不与外界,普通大众接触,又如何能发现真正的需求痛点,去解决它们呢?

这样的情况,在技术圈一般比较常见,就比如相声圈,在郭德纲出现之前,说相声的题材大都是“咱说相声的怎样怎样”,因为那些相声演员每天的工作生活就是跟自己的同行在一起研究相声,他们的眼界就在相声圈子之内,所以他们说的相声题材都在“相声”这个圈子里。

郭德纲和别的相声最大的区别就是和观众互动起来了,原来的相声基本都是自说自嗨。所以无法跳出相声原来的思维模式,那相声人自己讨论的事情,是不是都是服务于自己而嗨就可以了?

所以说,想要找点有用的东西,首先要走出自己的圈子,看看这世界需要些什么……

二. 成功的共性

经验一:找到真实的需求

虚假的需求,活动范围只在自己的圈子内,因此在开发产品时,并未认真去想,这个东西有多少实际价值?谁会去用它?有多大的市场价值?然后闭门造车,一门心思去开发。直到某天突然清醒的发现这个问题:谁会去为这个产品付费?想明白了这是个伪需求后,然后就烂尾了。因为所作的事是没有意义的。

经验二:将目标明确细分

目标太宏大,太宽泛,太遥远。看似有目标,但是太模糊,并未认真思量到底要做什么,所以没有详细的计划,里程碑什么都没有。

这点错误我在做博客的时候也犯过,最开始时想做个博客,但是却不知道要做个什么的?一会儿分享编程知识,一会儿又想让别人也使用,可以写博客,一会儿又想增加其他乱起八糟的功能,所以做了两次,都失败了,因为做的太过宽泛,定位不明确,自己也没有信息和兴趣坚持下去了。直到想明白了,才做了个简单的博客,这点或许可以称之为,想要的太多了!

经验三:多与外界沟通

一方面是当前使用的技术和现在最新技术的区别,如果有更优秀的技术,应该采用更加合适、更优秀的。

另一方面是市场现在需要什么技术,技术本身是为需求、为市场服务的,如果市场已经选择抛弃了某项技术,而你却仍然采用,那就无怪乎被市场抛弃了。

经验四:快速迭代,最小可用集

这个概念应该大家都知道,就是敏捷开发。如果想要将产品一次性完成之后,再去公开发布使用,这样就会导致项目周期非常长,很难坚持下去,另外,市场是变化的,等到做好之后,可能产品也就跟不上市场节奏了。

所以需要的是快速迭代,做到最基础的功能,然后立马拿出去见人,当然范围可以是有限的。然后根据
真正用户
的反馈,快速调整。不管技术好不好,哪怕产品差一点就差一点,先做出东西来,先用起来,功能先实现,业务先转起来,论证了这东西基本的可行性,再通过迭代去优化。这样即使产品真的不符合市场,也可以最小损失的立即调整方向。

经验五:做东西尽量不要藏着掖着

这一点和上面第三点和第四点还有点关系,在需求更新和开发时,不与外面沟通,这点是没有意义的,藏着掖着无非怕别人剽窃了自己的创意。但是你做出来产品还是要给大家用的。

先不论创意想法有多大实际价值,就算你的想法真的很厉害,如果你没有其它门槛,别人看了就会抄去,那你这个东西一定是会出问题的。没有其它门槛,被抄了是迟早的事情,藏着掖着没有用。做了东西就要勇敢拿出来和人交流,正面的就吸纳,负面的就多反思自己。

当然如果真火了,被人抄肯定是不爽的,但是不火的产品是没有任何用的,等到火了,在针对性采取措施,因为被抄是不可避免的。

经验六:学习

一个人做产品时肯定会碰到各种问题,遇到搞不定的问题,就必须去寻求解决方案,因为一个人要做很多事情,所以不断学习是必须的。

三. 如何寻找方向

热爱生活,观察身边人需要什么,深入思考分析。跳出自己的固有思维,非专业人士只管方不方便,顺不顺手,就像现在的手机,不到一岁的孩子都能找到看视频的软件,还会切换着看。

产品要通俗易懂,简单实用,就像流行歌曲一样,你觉得有深意,融合各种元素,但别人听不懂有卵用。口水歌,简单通俗,很快打开市场,艺人再慢慢打造不同的类型风格。

白居易写诗,喜欢让老头子老太太读或者听,看他们能不能听明白。能听明白了,他就定稿,所以他的诗都朗朗上口,简单通俗,流传的也广

面向用户需求编程成功快,很多东西只是你自己认为别人喜欢,别人并不是那么想的,而且人想要的和实际出来后,想法也往往大有不同。“想要的”是没有用过的,当真正得到后了解后就不要了,就像女人买东西。所以调查有作用,其实也很有限

做改变用户习惯的产品,那你的产品要更傻瓜直白

具体点可以按照以下几点去执行:

1.你可以去github上去看,那些star 多的。fork高的
2.去app store 里面找灵感,
3.根据你自身的需求出发,你有需求,或者你周围的人有需要,可能就是解决实际问题的软件。
4.要小而精,不要上来就搞什么平台,维护和更新是非常耗费精力的。

需求决定产品的方向,脱离了需求,产品将不被需要。

希望我们都能找到合适的突破点!

一、准备一个Docker的sqlserver

#创建挂载数据文件夹
mkdir -p /home/mssql/data#创建挂载日志文件夹
mkdir /home/mssql/log#给文件夹权限
chmod 777 /home/mssql/data
chmod
777 /home/mssql/log#创建mssql的docker容器 docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=sa123456." -e "MSSQL_AGENT_ENABLED=true" -p 1433:1433 --name mssql -h mssql \-v /home/mssql/data:/var/opt/mssql/data \-v /home/mssql/log:/var/opt/mssql/log \-d mcr.microsoft.com/mssql/server:latest

安装完通过SqlServer Management能连上了

二、配置分发服务器

这里分发服务器也是用数据库服务器充当,实际项目中如果有多余服务器可以用另外的服务器

1、创建发布服务器存快照的文件夹

#进入docker
docker exec -it mssql bin/bash#创建快照文件夹
mkdir /var/opt/mssql/ReplData#给权限
chmod 777 /var/opt/mssql/ReplData#退出容器
exit

2、错误的配置分发服务器做法

这里不要用数据库管理工具上面的配置分发服务器,
下面的是错误的做法

如果直接在界面配置的分发服务器,虽然能成功,但下发创建本地发布的时候会报"必须先将 xxx 作为发布服务器启用,然后才能创建发布。在下列对话框中,将此服务器作为发布服务器启用"

3、正确配置分发服务器方法

1 )查询数据库的所在docker的hostname

select @@servername

2)创建分发数据库

在数据库中执行

usemasterGO
EXEC sp_adddistributor @distributor = N'mssql'
GO
EXEC sp_adddistributiondb @database = N'distribution', @data_folder = N'/var/opt/mssql/data',@log_folder = N'/var/opt/mssql/data', @log_file_size = 2, @min_distretention = 0, @max_distretention = 72,@history_retention = 48, @deletebatchsize_xact = 5000, @deletebatchsize_cmd = 2000, @security_mode = 1,@password='sa123456.'
GO
SELECT name, create_date FROMsys.databasesGO
@distributor:上面查出的数据库的hostname
@database:分发数据库名称
其他参数代表什么意思在这里查看:https://learn.microsoft.com/zh-cn/SQL/relational-databases/system-stored-procedures/sp-adddistributiondb-transact-sql?view=sql-server-ver16

3)创建分发服务器
在数据库中执行
USE [distribution]

IF (NOT EXISTS (SELECT * FROM sysobjects WHERE name = 'UIProperties' AND type = 'U'))CREATE TABLE UIProperties(id INT)GO

IF (EXISTS (SELECT * FROM ::fn_listextendedproperty('SnapshotFolder', 'user', 'dbo', 'table', 'UIProperties', null, null)))EXEC sp_updateextendedproperty N'SnapshotFolder', N'/var/opt/mssql/ReplData', 'user', dbo, 'table', 'UIProperties'

ELSE

  EXEC sp_addextendedproperty N'SnapshotFolder', N'/var/opt/mssql/ReplData', 'user', dbo, 'table', 'UIProperties'

GO

EXEC sp_adddistpublisher @publisher = N'mssql', @distribution_db = N'distribution', @security_mode = 1,@working_directory = N'/var/opt/mssql/ReplData', @trusted = N'false', @thirdparty_flag = 0,@publisher_type = N'MSSQLSERVER'
@publisher:上面的hostname
@distribution_db:分发数据库名称
/var/opt/mssql/ReplData:这个路径为上面创建的路径

执行完就创建成功分发服务器了,能看到复制文件夹多出了一些菜单了


三、创建数据库发布

1、准备数据库
这里创建一个数据库 MyDb(主库)和创建一个数据库MyDbSlaver(从库,不建表,通过主库的快照同步)
如果是实际项目中,数据库的数据量已经很大的情况下,建议从库从主库还原一份,不要通过快照同步,因为这个过程会占服务器的cpu很高,会影响到服务器
然后给MyDb(主库)创建一个表测试用
CREATE TABLESysUser (
ID
INT PRIMARY KEY,
UserName N
VARCHAR(50),
Age
INT);

2、新建发布

选择事务发布,如想用其它发布类型,看对应说明

全部勾选

成功创建发布

四、创建数据库订阅

1、创建订阅

创建订阅成功

创建订阅成功后能看到从库把主库的表也同步过来了

2、验证效果

在MyDb操作的数据,在从库MyDbSlaver查到的结果是一样的



贝叶斯分类
是一种统计学分类方法,基于贝叶斯定理,对给定的数据集进行分类。
它的历史可以追溯到18世纪,当时英国统计学家托马斯·贝叶斯发展了贝叶斯定理,这个定理为统计决策提供了理论基础。

不过,
贝叶斯分类
在实际应用中的广泛使用是在20世纪80年代,当时计算机技术的进步使得大规模数据处理成为可能。

1. 算法概述

贝叶斯分类
基于贝叶斯公式,通过已知样本信息来计算未知样本属于各个类别的概率,然后选择概率最大的类别作为未知样本的分类结果。

贝叶斯公式
的简化公式:
\(P(A|B) = \frac{P(B|A)P(A)}{P(B)}\)
其中:

  1. \(P(A)\)
    :事件A发生的概率
  2. \(P(B)\)
    :事件A发生的概率
  3. \(P(A|B)\)
    :在事件B出现的前提下,A发生的概率
  4. \(P(B|A)\)
    :在事件A出现的前提下,B发生的概率

贝叶斯分类
就是基于这个公式扩展而来。
比如,一个具有
\(n\)
个特征的样本
\(x = (x_1, x_2, ..., x_n)\)
,该样本属于
K个
可能的类别
\(y_1,y_2,...,y_k\)

那么,任一个样本
\(x\)
属于某个类别
\(y_k\)
的概率为:
\(P(y_k|x) = \frac{P(X|y_k)P(y_k)}{P(x)}\)
根据这个模型,训练样本之后,就可以根据模型来预测某个
样本
属于哪个
类别

概率最大

这里讨论的贝叶斯分类算法,并没有考虑特征之间的关联关系,我们假设每个特征之间是相互独立的。
所以,这个算法也叫做
朴素贝叶斯分类

2. 创建样本数据

贝叶斯分类可以
这次用
scikit-learn
中的样本生成器
make_classification
来生成分类用的样本数据。

import matplotlib.pyplot as plt
from sklearn.datasets import make_classification

# 分类数据的样本生成器
X, y= make_classification(n_samples=1000, n_classes=4, n_informative=3)
plt.scatter(X[:, 0], X[:, 1], marker="o", c=y, s=25)

plt.show()

image.png
关于
样本生成器
的详细内容,请参考:
TODO

3. 模型训练

训练之前,为了减少算法误差,先对数据进行
标准化
处理(将数据缩放到
0~100
之间)。

from sklearn import preprocessing as pp

# 数据标准化
X = pp.minmax_scale(X, feature_range=(1, 100))
y = pp.minmax_scale(y, feature_range=(1, 100))

然后,分割
训练集

测试集

from sklearn.model_selection import train_test_split

# 分割训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

按照
8:2的比例
来划分训练集和测试集。

scikit-learn
中的朴素贝叶斯算法支持多种不同的分类器,
这些分类器基于不同的先验概率分布,适用于不同的数据类型和问题场景。
我们训练模型的时候要根据数据情况选择合适的分类器。

from sklearn.naive_bayes import (
    GaussianNB,
    MultinomialNB,
    ComplementNB,
    BernoulliNB,
    CategoricalNB,
)

reg_names = [
    "高斯朴素贝叶斯",
    "多项式朴素贝叶斯",
    "补码朴素贝叶斯",
    "伯努利朴素贝叶斯",
    "分类朴素贝叶斯",
]

# 定义
regs = [
    GaussianNB(),
    MultinomialNB(),
    ComplementNB(),
    BernoulliNB(),
    CategoricalNB(min_categories=101),
]

# 训练模型
for reg in regs:
    reg.fit(X_train, y_train)

各个分类器的简要说明:

  1. GaussianNB
    :基于高斯分布的朴素贝叶斯分类器。它假设每个特征服从高斯分布,即正态分布。这种分类器适用于连续型数据,特别是对于数值型特征。
  2. MultinomialNB
    :基于多项式分布的朴素贝叶斯分类器。它假设每个特征服从多项式分布,适用于离散型数据,特别是对于类别型特征。
  3. ComplementNB
    :基于互补分布的朴素贝叶斯分类器。它适用于离散型数据,特别是对于二元分类问题。
  4. BernoulliNB
    :基于伯努利分布的朴素贝叶斯分类器。它适用于二元分类问题,特别是对于二元特征或者二元输出。
  5. CategoricalNB
    :基于分类分布的朴素贝叶斯分类器。它适用于离散型数据,特别是对于类别型特征。

最后验证各个分类器的模型的训练效果:

# 在测试集上进行预测
y_preds = []
for reg in regs:
    y_pred = reg.predict(X_test)
    y_preds.append(y_pred)

for i in range(len(y_preds)):
    correct_pred = np.sum(y_preds[i] == y_test)
    print("【{}】 预测正确率:{:.2f}%".format(reg_names[i],
                                      correct_pred / len(y_pred) * 100))

# 运行结果
【高斯朴素贝叶斯】 预测正确率:82.50%
【多项式朴素贝叶斯】 预测正确率:75.00%
【补码朴素贝叶斯】 预测正确率:72.50%
【伯努利朴素贝叶斯】 预测正确率:22.00%
【分类朴素贝叶斯】 预测正确率:50.50%

这里虽然
高斯朴素贝叶斯
分类器的正确率最高,但不能就认为这种分类器是最好的。
只能说明
高斯朴素贝叶斯
分类器
最适合
分类上面随机生成的样本数据。
换成其他的样本数据,
高斯朴素贝叶斯
分类器的正确率就不一定是最高的了。

4. 总结

总的来说,
贝叶斯分类
是一种有效的分类方法,适用于对未知样本进行分类的问题。
它的应用范围广泛,可以处理多分类问题,也可以用于连续变量的分类。

贝叶斯分类
算法的主要优势在于:

  1. 是一种
    概率模型
    ,可以给出分类结果的概率,因此更加可靠和稳定。
  2. 可以处理
    多分类问题
    ,也可以用于
    连续变量的分类
  3. 实现
    相对简单
    ,可以在较短的时间内训练出模型并进行预测。

贝叶斯分类
算法也有其不足之处:

  1. 假设所有特征之间相互独立
    ,但在实际应用中这个假设往往不成立,因此会影响分类结果的准确性。
  2. 对于大规模的数据集,训练时间和
    预测时间可能会较长
  3. 对于数据的缺失和异常值处理
    不够鲁棒
    ,可能会对分类结果产生影响。