Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

Python——Python3新特性f-string

Formatted string literals


f-string

说明

  • 格式化的字符串文字以“f”为前缀
  • 类似于str.format()接受的格式字符串
  • 它们包含由花括号包围的替换字段
  • 替换字段是表达式,在运行时进行评估,然后使用format()协议进行格式化

工作原理

  • 从字符串中提取的表达式在f字符串出现的上下文中计算
  • 这意味着表达式可以完全访问本地和全局变量
  • 可以使用任何有效的Python表达式,包括函数和方法调用

与之前的表达式对比

  • 之前

    1
    print("%s%s%s" % (a, b, c+d))
  • f-string

    1
    print(f'{a}{b}{c+d}')

Python——Python3相对Python2的异同点总结

Python 3 和 Python 2 存在许多显著差异,下面为你详细介绍主要的不同之处:


语法层面

  • 打印函数 :
    • Python 2 里,print 属于语句,使用时无需括号。例如:print "Hello, World!"
    • Python 3 中,print 变为函数,必须使用括号。例如:print("Hello, World!")
  • 除法运算 :
    • Python 2 中,整数相除结果为整数,小数部分会被截断。例如:3 / 2 结果是 1
    • Python 3 里,整数相除结果为浮点数。例如:3 / 2 结果是 1.5。若想得到整数结果,需使用 // 运算符,如 3 // 2 结果是 1
  • Unicode 编码 :
    • Python 2 对 Unicode 支持欠佳,字符串默认是 ASCII 编码,若要使用 Unicode 字符串,需在字符串前加 u,如 u"你好"
    • Python 3 中,字符串默认是 Unicode 编码,可直接处理多种语言文字,无需额外指定

异常处理

  • 异常捕获语法 :
    • Python 2 可使用 except Exception, e 来捕获异常
    • Python 3 要求使用 except Exception as e 这种语法

迭代器与生成器

  • range 函数 :
    • Python 2 有 range 和 xrange 两个函数。range 返回列表,xrange 返回迭代器对象
    • Python 3 里,range 函数等同于 Python 2 的 xrange,返回迭代器对象,节省内存
  • 字典方法 :
    • Python 2 中,dict.keys()、dict.values() 和 dict.items() 返回列表
    • Python 3 里,这些方法返回视图对象,是迭代器,并非列表

模块和库

  • 标准库变动 :
    • Python 3 中部分模块的名称和位置有所改变。例如,urllib 模块被拆分成多个子模块,像 urllib.request、urllib.parse 等
    • 一些 Python 2 的库在 Python 3 中需要重新安装或更新以确保兼容

其他Python3特性

  • Python3还提供了比如 f-string 等新特性(允许使用print(f'{a}{b}{c+d}')这种代码来组织字符串)

示例代码对比

  • Python 2 代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 打印语句
    print "Hello, Python 2!"

    # 除法运算
    result = 3 / 2
    print result

    # 异常处理
    try:
    num = 1 / 0
    except ZeroDivisionError, e:
    print "Error:", e

    # range 函数
    for i in range(5):
    print i

    # 字典方法
    my_dict = {'a': 1, 'b': 2}
    print my_dict.keys()
  • Python 3 代码示例 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 打印函数
    print("Hello, Python 3!")

    # 除法运算
    result = 3 / 2
    print(result)

    # 异常处理
    try:
    num = 1 / 0
    except ZeroDivisionError as e:
    print("Error:", e)

    # range 函数
    for i in range(5):
    print(i)

    # 字典方法
    my_dict = {'a': 1, 'b': 2}
    print(my_dict.keys())
    • 在捕捉多个异常时可以使用
      1
      except (ZeroDivisionError, ABCError) as e:

附录:Python 3 可以指定参数类型

  • Python 3 可以指定参数类型(Python2不可以),不过这属于类型提示(Type Hints),它只是一种提示,并不会在运行时强制检查参数类型
  • 虽然类型提示不会在运行时进行强制检查,但它能提升代码的可读性和可维护性,同时还能让 IDE 提供更精准的代码提示和错误检查。若要在运行时进行类型检查,可以使用第三方库,例如 pydantic
  • 下面从不同方面介绍 Python 3 中指定参数类型的方式

函数参数类型提示

  • 在定义函数时,可以为参数和返回值指定类型,示例如下:

    1
    2
    def add_numbers(a: int, b: int) -> int:
    return a + b
    • 在上述代码里,a: int 和 b: int 表明 a 和 b 应当是整数类型,-> int 表示该函数返回值为整数类型

类属性类型提示

  • 在类中,也能够为属性指定类型,示例如下:

    1
    2
    3
    4
    5
    6
    7
    class Person:
    name: str
    age: int

    def __init__(self, name: str, age: int):
    self.name = name
    self.age = age
    • 在这个示例中,name: str 和 age: int 分别指定了 Person 类的 name 和 age 属性的类型

复杂类型提示

  • Python 还提供了 typing 模块,借助该模块可以进行更复杂的类型提示,例如列表、字典等。示例如下:

    1
    2
    3
    4
    5
    6
    7
    from typing import List, Dict

    def process_list(numbers: List[int]) -> int:
    return sum(numbers)

    def process_dict(data: Dict[str, int]) -> int:
    return sum(data.values())
    • 在上述代码中,List[int] 表示列表中的元素应当是整数类型,Dict[str, int] 表示字典的键为字符串类型,值为整数类型

可选类型和联合类型

  • 使用 typing 模块的 Optional 和 Union 可以表示可选类型和联合类型。示例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    from typing import Optional, Union

    def get_length(s: Optional[str]) -> int:
    if s is None:
    return 0
    return len(s)
    print(get_length("Alice")) # OK
    print(get_length(None)) # OK
    print(get_length([1,2,3])) # 运行正常,但编译器会有提示

    def convert_to_number(value: Union[int, str]) -> int:
    if isinstance(value, str):
    return int(value)
    return value
    print(convert_to_number("112")) # OK
    print(convert_to_number(112)) # OK
    print(convert_to_number(112.0)) # 运行正常,但编译器会有提示

    # 5
    # 0
    # 3
    # 112
    # 112
    # 112.0
    • 在 get_length 函数里,Optional[str] 意味着参数 s 可以是字符串类型,也可以是 None
    • 在 convert_to_number 函数中,Union[int, str] 表示参数 value 可以是整数类型或者字符串类型

Python——正则表达式


将文本分行

  • text.split("\n")
    • 适用于普通文本,这种文本的特点是写到文件或者print输出后看不到\n字符串,自动转义成换行符,显示出来就是分行的
  • text.split("\\n")
    • 适用于被编码后的文本,该文本的特点是经过编码,所以写到文件或者输出时还能看到\n字符串,本质上需要二次转义才能显示为换行符号
  • text.split(r"\n")
    • 同上,等价与告诉别人不需要转义r"\n"本身代表着"\n"是原始文本,无需转义,体现在分
  • text.decode("unicode-escape").split("\n")
    • 效果同上
  • 第一种文本是一次转义就能按行显示的文本,比如一次print和write操作都会转义
  • 后三种文本需要两次转义才能按行显示,中间两种分割方式等价,最后一种是先转义再分割

正则表达式匹配完整字符串

  • 必须使用^和$, 否则部分匹配也会返回结果
    1
    2
    3
    4
    5
    import re
    def totally_match(pattern, string):
    if re.match(pattern, string) is not None:
    return True
    totally_match(r"^cat$", "cat")

正则表达式替换指定范式的字符

  • 要求:替换问题[数字1]:[数字2]为公式[数字1]。[数字2],即其中问题和:替换为公式和。,并保留[数字]部分
    • 比如:替换问题1:100为公式1。100
  • 实现方式:
    1
    2
    3
    4
    5
    import re
    text = "问题1:100 这是一个示例"
    # r'问题(\d+):(\d+)',(\d+) 是两个捕获组,分别用于捕获数字1和数字2,替换为:r'公式\1。\2',解释:\1 表示第一个捕获组的内容(数字1),\2 表示第二个捕获组的内容(数字2)
    result = re.sub(r'问题(\d+):(\d+)', r'公式\1。\2', text)
    print(result)

附录:高阶正则表达式

  • 整体来说,比较常用的正则表达式如下:
    语法 名称 功能
    (?=...) 正向前瞻 后面必须是指定的模式
    (?!...) 负向前瞻 后面不能是指定的模式
    (?<=...) 正向后瞻 前面必须是指定的模式
    (?<!...) 负向后瞻 前面不能是指定的模式
    (?:...) 非捕获组 组合但不捕获
    (?P<name>...) 命名捕获组 捕获并命名
    (?>...) 原子组 匹配后不回溯
    (?(...)...) 条件匹配 根据条件匹配不同模式
  • 其中(?...)是一种特殊的模式,其他字符表示如下:
    • =表示匹配(对应为正),相反的是!表示不匹配(对应为负)
    • <表示向后匹配,向后看(后瞻),相反的是不使用<,则表示向前看(前瞻)

正向前瞻(Positive Lookahead)

  • 语法 :(?=...)
  • 功能 :匹配某个位置,要求该位置后面必须是指定的模式
  • 示例 :
    • 正则表达式:\d(?=px)
    • 匹配:数字后面必须是 px,但只匹配数字
    • 输入:10px 20em
    • 匹配结果:1(在 10px 中),2(在 20em 中不匹配,因为后面是 em)

负向前瞻(Negative Lookahead)

  • 语法 :(?!...)
  • 功能 :匹配某个位置,要求该位置后面不能是指定的模式
  • 示例 :
    • 正则表达式:\d(?!px)
    • 匹配:数字后面不能是 px,但只匹配数字
    • 输入:10px 20em
    • 匹配结果:0(在 10px 中不匹配,因为后面是 px),2(在 20em 中)

正向后瞻(Positive Lookbehind)

  • 语法 :(?<=...)
  • 功能 :匹配某个位置,要求该位置前面必须是指定的模式
  • 示例 :
    • 正则表达式:(?<=\$)\d+
    • 匹配:前面是 $ 的数字
    • 输入:$100 €200
    • 匹配结果:100($100 中的数字),200 不匹配,因为前面是 €

负向后瞻(Negative Lookbehind)

  • 语法 :(?<!...)
  • 功能 :匹配某个位置,要求该位置前面不能是指定的模式
  • 示例 :
    • 正则表达式:(?<!\$)\d+
    • 匹配:前面不是 $ 的数字
    • 输入:$100 200
    • 匹配结果:200($100 中的 100 不匹配,因为前面是 $)

非捕获组(Non-capturing Group)

  • 语法 :(?:...)
  • 功能 :匹配部分符合预期的字符,但不捕获匹配的内容(即不会生成反向引用)
  • 示例 :
    • 正则表达式:(?:https?://)(\w+)
    • 匹配:http:// 或 https:// 后面的单词,但只捕获单词部分
    • 输入:http://example.com https://test.com
    • 匹配结果:example 和 test

命名捕获组(Named Capturing Group)

  • 语法 :(?P<name>...)
  • 功能 :将匹配的内容捕获到一个命名组中,可以通过名称引用
  • 示例 :
    • 正则表达式:(?P<year>\d{4})-(?P<month>\d{2})
    • 匹配:日期中的年份和月份,并分别命名为 year 和 month
    • 输入:2023-10
    • 匹配结果:year=2023,month=10

原子组(Atomic Group)

  • 语法 :(?>...)
  • 功能 :将组内的匹配作为一个原子操作,一旦匹配成功,就不会回溯
  • 示例 :
    • 正则表达式:(?>a+)ab
    • 匹配:一个或多个 a,后面必须是 ab,且不会回溯
    • 输入:aaab
    • 匹配结果:无匹配,因为(?>a+)先匹配了aaa,导致正则表达式的ab无法与b匹配,此时不再回溯
    • 注:作为对照,正则表达式 a+ab可以匹配aaab,因为a+先匹配了aaa,正则表达式的ab无法与b匹配,此时会回溯到a+先匹配了aa,正则表达式的ab与ab匹配

条件匹配(Conditional Matching)

  • 语法 :(?(condition)true-pattern|false-pattern)
  • 功能 :根据条件匹配不同的模式
  • 示例 :
    • 正则表达式:(?(?<=foo)bar|baz)
    • 匹配:如果前面是 foo,则匹配 bar,否则匹配 baz
    • 输入:foobar testbaz
    • 匹配结果:bar(在 foobar 中),baz(在 testbaz 中)。

NLP——LLDA的Gibbs采样实现


整体说明

  • 论文介绍基于 Python 的有标签隐式狄利克雷分布(Labeled Latent Dirichlet Allocation,Labeled-LDA,L-LDA,LLDA)的 Gibbs Sampling 实现
  • 论文同时还记录了项目实现中的一些思考和笔记,作为个人 Labeled LDA 实现过程中遇到的问题和论文中的思考,写得比较多,比较杂
  • 项目地址:Labeled-LDA-Python
  • 原始文章:Labeled LDA: A supervised topic model for credit attribution in multi-labeled corpora

为什么选择吉布斯采样

  • 详细问题:为什么使用Gibbs Sampling而不是变分推断实现?
  • 答:这个项目是参考 L-LDA 原始文章实现的,文章中只给出了 Gibbs 采样的实现,理论上也可以使用变分推断实现

收敛性的判断

  • 补充问题:收敛性的判断使用参数变化量还是使用困惑度?

收敛的多种判断方式

  • 采样一定次数后停止
    • 网上太多实现使用的是采样循环(对所有词的主题的完全采样一次算一个循环)一定次数后停止
    • 优点:
      • 每次循环结束无需任何额外的计算和判断,只需把迭代次数加一即可
      • 无需存储参数的中间信息
    • 缺点:
      • 难以确定采样循环次数,次数太多浪费时间,次数太少容易造成不收敛
  • 参数变化量(Gibbs 采样收敛性的判断使用这个感觉更靠谱)
    • 基本原理是: 采样收敛以后概率不变,模型参数也会收敛,两次迭代之间参数变化量会很小
    • 优点:
      • 可以通过参数变化来精确知道模型是否收敛
    • 缺点:
      • 每次采样循环结束后需要计算参数变化量
      • 需要存储之前的参数的中间结果结果(参数是所有概率矩阵,往往并不小)
  • 困惑度的变化量
    • 在采样的过程中,模型的困惑度一定是递减的(偶尔可能有微弱的增加,属于正常现象)
    • 当困惑度小到一定程度后困惑度应该趋近于收敛,几乎不变,如果困惑度不再有大的变化,我们认为模型收敛
    • 优点:
      • 无需存储参数,仅仅存储困惑度的值即可(是一个标量)
    • 缺点:
      • 每次评估困惑度时计算量大
      • 当前尚未看到有人使用这个指标作为语言模型收敛度的判断
  • 在我们的场景中,因为我们想知道每一轮的困惑度,所以就顺便把困惑度的变化量当做收敛性判断实现了
  • 实际上在训练时我们为了收敛保证,选择的是采样次数在一定范围内(不能太小,也不能太大),同时参数变化量变得很小的双重判断标准

我们的实现

  • 实现了困惑度评估收敛性的方法: 滑动窗口保留近 N 轮困惑度(每一轮针对所有词),模型困惑度都不变化,那么认为该模型收敛
    • 在不同的实际应用中收敛性的判断应该使用不同的方式,我们这里使用困惑度是因为反正都要计算困惑度的,为了充分利用困惑度的计算结果,顺便将其作为收敛性判断
  • 实际论文和工程实验中为了保险使用的是迭代次数的方式
  • 测试说明:经测试发现,模型的困惑度 10 次采样前后变化不大后,参数的确收敛了,而且模型的预测效果也收敛,继续采样 50次 循环对模型的效果无明显提升

增量更新训练数据

  • 补充问题:在向原始训练数据集(TDS)中添加新的训练数据(NewTDS)时,如何利用原始训练数据上一次的训练结果?
  • 我们实现了一种可以增量更新模型的方式:将上一次模型(TDS训练得到)已经训练好的参数作为新模型的初始参数,提升收敛速度
  • 由于 Gibbs 采样的收敛状态与初始状态无关,所以我们可以从数学理论上证明该方法的正确性
  • 测试说明:
    • 经测试发现,本方法可以很大程度提升新模型的收敛速度
    • 经测试发现,本方法训练的结果与随机初始化参数的训练结果,模型效果相同
      • 两个模型的困惑度收敛到近似相等
      • 实际场景中测试说明两个模型的精度也相同

推断(预测)

  • 补充问题:模型训练收敛后,对新来的文档,如何给出主题预测?
  • 参考文献: LDA数学八卦,L-LDA
  • 在训练阶段得到模型的主题-词矩阵 \(\beta\) 后,可以继续进行采样
  • 不用存储文档-主题 \(\theta\) 矩阵,采样时不需要这个参数,训练后这个参数无需存储
  • 流程如下:
    • 随机初始化, 对文档中的没个词随机赋一个主题
    • 重新扫描当前文档,按照吉布斯采样公式:
      $$
      \begin{align}
      P(z_{i}=k|\vec{z}_{\not{i}}, \vec{w}) &\propto E(\theta_{m,k}) E(\phi_{k,t}) \\
      &= \hat{\theta}_{m,k} \hat{\phi}_{k,t}
      \end{align}
      $$
      • \(\vec{z}_{\not{i}}\) 为除了 \(z_{i}\) 的所有当前主题 \(z_{i}\) 是词 \(w_{i}\) 的主题
      • \(\hat{\theta}_{m,k} \hat{\phi}_{k,t}\) 是样本均值,这里是用当前均值估计期望
      • 显然对每个词,重新采样它的主题(每次采样时实际上当前词的主题只与当前文档其他词的主题和主题-词矩阵相关, 这里推断和训练期间都一样)
      • 实际实现时, 我们并没有存储参数 \(\hat{\phi}_{k,t}\) 的值,而是存储一个主题-词[数量]矩阵,方便计算和采样,这种实现推断期间 \(\hat{\phi}_{k,t}\) 的值还会继续变化,实际上是更符合实际(精确)的做法,这种做法是使得当前推断和训练的采样方法一模一样(连参数 \(\hat{\phi}_{k,t}\) 的计算都一样,论文推荐的是在推断期间 \(\hat{\phi}_{k,t}\) 值不变,与训练步骤不同)
        • 这里也可以把 \(\hat{\phi}_{k,t}\) 的参数(整个矩阵)都存储下来,然后推断期间都不变,这种做法是论文中推荐的,这种做法采样速度快,不需要每次都计算一下当前的 \(\hat{\phi}_{k,t}\) 值,但是实际上并不精确(当然:当训练数据非常大时,这里近似于精确的,我们的应用场景中考虑到可能训练数据一开始并不多,所以确保精确,我们实现的是前面那种更精确的做法)
    • 重复扫描采样直至收敛
    • 统计文档中的主题分布,得到新文档的文档主题分布 \(\vec{\theta}_{new}\)

实际使用中主题数如何确定的?

  • 一般情况下可以使用困惑度,不同的主题数对应模型的困惑度不一样,k 从小到大,对应模型的困惑度应该是先减小后增加,选择困惑度最小的模型对应的主题数即可
  • 在我们论文实验中,由于主题数与标签数量一致,直接设定即可

验证集问题

  • 补充问题:实际在 SemiTagRec 实现的时候,只提到训练集和测试集,那网格搜索的时候验证集是什么数据呢?
  • 我们的算法中,如果考虑详细情况的话,网格搜索超参数其实需要每次都调一遍
  • 但是幸亏我们测试发现 Integrator 的超参数很容易调
  • 实际实验时,我们使用 80% 和 90% 样本作为训练集,然后只用随机采样 50 个左右的样本作为验证集基本就收敛了(随机采样多个都是这样,能在 0.91 到 0.09 周围得到最优值),而且后面调试几乎不变(基本上就在 0.89-0.93 和 0.11-0.07 之间且精度几乎没变化,所以我们实际上用所有测试样本测试通过得到了最优的值为0.91和0.09,这个值是取平均值得到的)
  • 最终方法 :在我们的算法中,首先经过多次训练和测试(实际上就是每次训练完,然后使用网格搜索法完成了 Integrator 的超参数设定,基本都是使用 0.91和0.09最好),然后接下来的模型迭代训练中我们没有再修改这个超参数了,所以也不用验证集了,这可以为我们之后的训练增加训练和测试数据,对我们来说是个好消息

训练集和测试集的划分

  • 为什么使用90%这么多的样本作为训练集?测试集使用10%足够了吗?
  • 实际上我们的算法中,我们一开始的分割方式是60%训练,20%验证,20%测试,这样训练的到的结果不理想(Labeled LDA的训练结果测试就非常差),分析原因其实是3000+ 的训练集太少了,对我们的训练模型Labeled LDA来说,远远不够
  • 由于可用的训练样本数量太少,为了保证训练质量,我们选择90%(5000个左右)用于训练后,10%(550个左右)用于测试,550个测试样本总数有 2000+个正确的标签,实际上够用了(多次测试证明,随机选取 300 个测试样本以后基本上增加测试样本模型的精度几乎没变化)

为什么不使用十折交叉验证法

  • 补充问题:既然分配给测试的数据太少,为了增加训练集的同时保证模型评估的精确性,是不是应该使用十折交叉验证法
  • 我们的模型使用的训练时间太长了,采样花的时间比较多,考虑到时间因素,没有使用十折交叉验证
  • PS:许多文章也都是这样直接划定测试集和训练集的

为什么使用多处理器?

  • 使用多处理器的初始想法(这是一个错误的想法)
    • 预测时文本的预测结果与初始值(当前文本每个词的主题初始分配)有很大关系,不同初始值会收敛到不同的结果,为了防止初始值不同带来的误差(错误,这里实际上是采样还没收敛才导致的),我们采用多次初始化并且多次收敛的方法,最终对结果求均值,得到最终的预测结果
  • 问题:吉布斯采样应该是能够收敛到目标分布的,为什么预测时不会收敛到相同目标分布
  • 回答:会收敛的,每次迭代次数不要太少,迭代一定次数后开始丢弃之前的一定数量的采样,去后面的采样平均值,会得到收敛的结果,核心是采样次数一定要够
  • 进一步实验证明:采样次数不足时多个不同初始值采样的结果取平均的确是有帮助的,但是采样次数非常多以后就不需要了,采样结果收敛后一定是到那个目标分布的!

多处理器依然存在的意义

  • 用于对相同文档同时采样,收敛后每个处理器返回自己的一个平稳分布的样本(可以为一个,可以为多个),然后所有样本构成最终平稳分布的代表样本
    • 实际上收敛后这些样本都是服从目标分布产生的
  • 实现上没有问题,但是这里浪费了很多时间采样到收敛的过程(多条不同的采样过程分别采样到收敛,很浪费时间)
  • 从单个处理器来看,不论怎样都需要采样到平稳分布的,在多处理器同时工作时,从平稳状态中采样平稳分布的样本需要的采样次数实际上是被均分到多个处理器上的,从这里来看不考虑内存占用等方面的问题,多处理器工作的确是能节约我们的整体时间的
  • 一般为了避免随机变量统计量(如期望等)估计的偏差,需要产生独立同分布的样本,我们这里就需要: 同时使用多条马尔可夫链可以得到独立同分布的样本,否则同一条链上的样本往往不是独立的,因为同一条链上的后一个样本由前一个样本通过某种特定的状态转移概率得到.
    • 实践中,在同一条马尔可夫链上每隔若干个样本才选取一个可以得到近似独立的样本
    • 如果仅仅是采样,不需要样本间相互独立,我们一般就直接使用一条链产生多个样本即可

为什么训练的时候不是采样多个样本来预测分布

  • 补充问题:为什么训练的时候不使用采样多个样本(每个样本代表当前所有文档的所有词的主题矩阵)?
  • 由于训练样本非常多,所以单个样本足以代表整体主题-词分布
    • 因为我们不评估每个文档的主题分布:由于对每个文档来说,不取多个采样值,无法代表文档本身的词分布
    • 而是关注每个主题的词分布:实际上对每个主题来说,词的数量已经非常多了,完全可以代表当前主题-词分布了
    • 经过测试证明的,也有论文支持

测试

  • 训练收敛后取后面 m 个样本作为训练结果计算主题-词分布与收敛后取最后一个样本得到的结果(主题-词分布矩阵)相同
  • 所以为了节约内存和减少计算量,我们只保留了最后一个样本,丢弃了前面的样本
  • 论文原文引用说明 Parameter estimation for text analysis,Gregor Heinrich

    To obtain the resulting model parameters from a Gibbs sampler, several approaches exist. One is to just use only one read out, another is to average a number of samples, and often it is desirable to leave an interval of L iteration between subsequent read-outs to obtain decorrelated states of the Markov chain. This interval is often called “thinning interval” or sampling lag.

  • 说明:上面这段话的是针对 LDA 的 Gibbs 采样方法而言的(虽然上面这段话没提到LDA)
  • 上面的引用说明选取LDA收敛后的训练样本选择有两种方式:
    • 选取一个作为样本(这里只有在LDA训练时可用这种方法,其他的采样模型还要视情况而定的)
      • 在LDA中,由于训练样本非常多,所以单个样本足以代表整体主题-词分布(由于LDA同时还关注着文档-主题分布,所以在LDA中私以为还是采样多个样本保险一些,特别是对于词数比较少的文档)
      • 但是,在我们的应用场景中(在Labeled LDA中),
        • 只关注每个主题的词分布:实际上对每个主题来说,词的数量已经非常多了,完全可以代表当前主题-词分布了,
        • 训练时我们不评估每个文档的主题分布
        • 预测时:由于对每个文档来说,不取多个采样值,无法代表文档本身的词分布(特别是当文挡中的词比较少时),所以后面的预测过程中对单个文本的预测问题我们需要采样多个收敛后的样本计算均值
    • 选取多个样本的平均值作为样本
      • 注意:选取多个样本时,为了得到马尔可夫模型不相关的状态,需要间隔L次迭代进行间隔采样
      • 一般来说,在Markov chain收敛后开始从1计数,Gibbs采样(这里不针对LDA)选取一次完整迭代后的结果作为平稳分布的样本即可
        • 也就是 \([(x_{1}^{t},x_{2}^{t},\cdots,x_{n}^{t}), (x_{1}^{t+1},x_{2}^{t+1},\cdots,x_{n}^{t+1}),\cdots, (x_{1}^{t+s},x_{2}^{t+s},\cdots,x_{n}^{t+s})]\)
          • 其中需要的平稳分布的样本数是(s+1)
          • 注意 \([(x_{1}^{t+1},x_{2}^{t},\cdots,x_{n}^{t}), (x_{1}^{t+1},x_{2}^{t+1},\cdots,x_{n}^{t})]\) 这些不完整迭代的结果都不能成为平稳分布的样本,因为这些样本之间相关度太高,不够独立,不能用来代表最终的平稳分布,容易造成局部偏差

论文中为什么选择Labeled LDA而不使用深度学习

  • 补充提问:为什么使用Labeled LDA模型,没有考虑过使用深度学习模型吗?
  • TLDR:主要原因是效果不好
  • 写论文需要可解释性,当时在大家的视角看,深度学习模型的解释性远远不如 Labeled LDA 模型的解释性
  • 本人使用深度学习模型测试:没拿到优于 Labeled LDA 模型的效果
    • 模型较简单,简单的对每个词 OneHot 编码,然后+固定分类类别数量为 1000,所以输出为 1000 维度
    • 模型是对 NNLM 的一种改进
    • 损失函数使用的是交叉熵
  • 深度学习在我们的场景中效果不好的原因可能包括以下方面:
    • 训练数据量不够,太稀疏
    • 没找到合适的神经网络模型,比如可以考虑使用一些权重共享的思想,降低由于数据太少引起的过拟合
    • 在未来的想法: 如果可以增加数据量,或者能够使用一些新的有效模型或者词嵌入的数据集,可以重新尝试使用神经网络模型

NLP——关于英文单词的处理总结


保留词根

  • 安装相关库:

    1
    pip install pattern
  • 导入和使用

    1
    2
    3
    4
    5
    from pattern.text.en import lemma
    lemma("describing")

    # output:
    # describe
    • 需要nltk中的几个语料库包, 如果没有以下包,导入时会报出zip文件相关的错,按装这几个语料库包直接使用nltk.download(“wordent”)等语句就行
      1
      "wordnet", "wordnet_ic", "sentiwordnet"

保留词干

  • 安装相关库:
    • 安装 nltk 即可
  • 导入和使用:
    1
    2
    3
    4
    5
    6
    7
    from nltk.stem.porter import PorterStemmer

    stemmer = PorterStemmer()
    stemmer.stem("describing")

    # output:
    # describ

Linux——代理使用教程


整体说明

  • 本文以 CentOS 的终端中配置代理为例
  • 通过设置环境变量来实现
  • 某些工具的代理比较特殊,可能需要单独配置,本文不考虑这种情况

临时生效(当前终端会话)

  • 在终端中直接执行以下命令(根据你的代理类型和地址修改):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # HTTP 代理
    export http_proxy="http://代理服务器IP:端口"
    export https_proxy="http://代理服务器IP:端口"

    # 如果代理需要认证
    export http_proxy="http://用户名:密码@代理服务器IP:端口"
    export https_proxy="http://用户名:密码@代理服务器IP:端口"

    # SOCKS5 代理
    export all_proxy="socks5://代理服务器IP:端口"

    # SOCKS5 代理(需认证)
    export all_proxy="socks5://用户名:密码@代理服务器IP:端口"

永久生效(对当前用户,常用)

  • 编辑用户目录下的 .bashrc 文件,在文件末尾添加上述代理设置命令:

    1
    vi ~/.bashrc
  • 使配置生效:

    1
    source ~/.bashrc

永久生效(对所有用户)

  • 如果需要让所有用户都使用代理,可以修改 /etc/profile 文件:

    1
    sudo vi /etc/profile
  • 添加代理设置后执行:

    1
    source /etc/profile

验证代理是否生效

  • 可以使用 curl 命令测试:

    1
    curl -I http://www.google.com
  • 如果返回 HTTP 状态码(如 200、301 等),说明代理配置成功


取消代理

  • 如果需要临时取消代理,可以执行:
    1
    2
    3
    unset http_proxy
    unset https_proxy
    unset all_proxy

Linux——进程相关操作


批量杀死进程

  • 杀死某个用户的所有进程
    1
    pkill -u jiahong

top查看进程信息

  • M: 根据驻留内存大小进行排序
  • P: 根据CPU使用百分比大小进行排序
  • f: 每列的参数说明

查看进程的堆栈

  • pstack
  • gstack

杀死特定进程

已知进程pid

  • 杀死进程

    1
    kill [pid]
  • 强制杀死进程

    1
    kill -9 [pid]

未知进程pid

  • 如果不知道进程pid可以在top命令里面找COMMAND列中相关的进程

  • 有时候通过top的信息不方便找到(比如想通过启动命令查找,比如想进程太多,找不过来)

    • 此时通过下面的命令查找

      1
      ps -aux | grep "command"
    • 更多ps -aux的解析可以参考博客https://www.cnblogs.com/dion-90/articles/9048627.html


htop查看进程

1
2
apt intall htop
htop
  • 比top更优秀的进程查看工具

操作系统——同步vs异步-阻塞vs非阻塞

  • 参考链接
    • https://www.zhihu.com/question/19732473/answer/20851256
    • https://www.zhihu.com/question/26393784/answer/513257548

同步与异步

  • 同步与异步关注的是消息通信机制 :被调用者是否有回传功能在任务结束时通知调用者

    同步

  • 同步 :就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了
    • 比如写一个函数调用另一个函数,必须等待返回结果才继续下一步骤,因为同步调用是被调用者没有回调通知的功能,所以必须等

异步

  • 异步 :调用在发出之后,这个调用就直接返回了,所以没有返回结果
  • 调用者不会立刻得到结果,被调用者完成工作后会通知调用者【通过回调函数等方式】
  • 比如在Future和Callable配合使用时
    • 调用者可以启动Callable对应的线程执行任务,然后马上返回,获取到一个Future对象
    • Callable完成工作后,会将返回值存放到Future【相当于一个回调】
    • 调用者可以通过检查Future对象,调用其.get()方法得到返回值
      • 【如果没有返回的话,会被阻塞,但是启动任务和调用get()期间,调用者是可以做自己的事情的,所以是异步非阻塞调用,然后get()阻塞式接受结果】

区别分析

  • 同步与异步的重点区别在于是否有返回通知的功能:https://www.zhihu.com/question/26393784/answer/513257548
  • 是否马上返回感觉只是一个附属的结论,因为异步能通知,所以才能马上离开

阻塞与非阻塞

  • 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态 :被调用者是否被阻塞

    阻塞

  • 阻塞 :阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回
    • 如果被调用者没有完成工作,就一直等待

非阻塞

  • 非阻塞 :非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程
    • 如果被调用者还没有完成工作,就返回默认值

区别分析

  • 同步与异步的区别在于等待结果时的状态 :https://www.zhihu.com/question/19732473/answer/20851256

写在最后

  • 同步一般伴随着阻塞,因为同步时不等待的话拿不到结果
    • 没见过同步的非阻塞是调用,除非是不需要结果的调用,只是为了发送一个通知给被调用者
  • 异步一般伴随着非阻塞,不然就浪费了异步的功能
    • 异步调用后可以选择两种调用拿到结果
      • 阻塞式get + 轮训
      • 阻塞式get

Python——ProcessPoolExecutor和ThreadPoolExecutor

Python 中如何使用CPU的多个核


全局解释器锁(GIL)

  • 由于CPython解释器本身就不是线程安全的,所以需要一个全局解释器锁,以保证同一时刻仅有一个线程在执行Python的字节码
  • 由于GIL的存在,造成了Python多线程不是不能真正并行,尽管有多个CPU核心也不能全都用上
  • 由于标准库中执行所有阻塞型IO操作的函数,在等待操作系统返回结果时都会释放GIL,这意味着Python在这个层次上可以使用多线程,所以对于IO密集型任务来说,多线程是有作用的

ThreadPoolExecutor

  • 使用多线程,适用于IO密集型的任务
    1
    2



ProcessPoolExecutor

  • 使用多进程,突破GIL的限制,绕开GIL,成功使用多个CPU核
    1
    2


Docker——Docker访问GPU笔记


NVIDIA Docker 工具链

  • 若直接安装 docker 并启动 GPU 程序,会出现报错

  • 比如,下面使用 Megatron 官方教程的命令(会使用到 GPU):

    1
    sudo docker run --ipc=host --shm-size=512m --gpus 2 -it nvcr.io/nvidia/pytorch:24.02-py3
  • 执行上面的命令时出现下面的错误

    1
    docker: Error response from daemon: could not select device driver "" with capabilities: [[gpu]]
  • 出现上面问题的原因是,Docker 无法找到合适的 GPU 驱动程序

    • Docker 访问 GPU 需要专门为 Docker 做的工具链 NVIDIA Docker
    • 上述问题一般是:NVIDIA Docker 工具链未正确安装或配置导致的
  • NVIDIA Docker 工具链安装方式:

    • 第一步:确保 NVIDIA 驱动已经安装

      1
      nvidia-smi
      • 若未安装,则需要先安装驱动,Ubuntu系统可参考:Ubuntu——显卡驱动安装
    • 第二步:安装 NVIDIA Docker 工具链

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      # 添加NVIDIA的包仓库
      distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
      curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
      curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list

      # 安装nvidia-container-toolkit
      sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit

      # 重启Docker服务
      sudo systemctl restart docker
1…313233…61
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

608 posts
49 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4